chore: release as 3.0.0 and write down the BC break #18 shipped - #28
Conversation
`Client::VERSION` still read '2.1.2' and the changelog entry carried no version heading at all, while `Price::fromArray()` - public, documented, and callable directly - went from total to partial in #18. An integrator reading either signal would conclude nothing in their code could break. - `Client::VERSION` is 3.0.0. The change shipped to main labelled as a patch; this corrects the label rather than re-releasing the code. - CHANGELOG gets a version heading and a `### Breaking changes` section: a table of exactly what throws now that did not before, what is still preserved (legitimate zero and negative prices, an empty `prices` list, a row with no timestamp), and the code a caller writes to adapt. - README gets an "Upgrading to 3.0" section making the same point where a caller will actually see it. - `VersioningTest` fences all of it: the version is semver, its major is past the last BC-compatible line, the topmost changelog entry names a version and matches `Client::VERSION`, that entry documents the break and what it throws, and the User-Agent carries the same version it claims. PHPStan is deliberately NOT wired in here. Level 9 reports 17 errors on src/, and six of them are the `(string)` casts being removed in the #21 PR while an eleventh is in the transport being rewritten in the #22 PR. Adding it now means either a baseline that conflicts with both, or fixing Client.php in a PR about version numbers. Inventory posted to #24 instead, to land after those two. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ao5paex73xXvuM424Libo
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
The section documented only the #18 break. A reader deciding whether it is safe to upgrade reads "### Breaking changes" and nothing else, so a break recorded only under "Fixed" or "Security" is one they meet in production. Added to the table: `currency` is now required, a non-string `currency`, and a non-string `unit`/`name`/`source`/`type`/`formatted` (#21). Split the single "unparseable timestamp" row into the two distinct cases #20 introduced - a value PHP repaired into a plausible date, and a relative expression such as `now` - because #18 already rejected the genuinely unparseable and conflating them understates what changed. Added as their own entries: redirects are no longer followed (#22), and the new `lib-curl >= 7.58.0` platform requirement, which is an install-time break on an old host and appears nowhere else a reader would look. Called out the two rejections a reader would not predict: leap seconds, and naive timestamps read as UTC rather than host-local. Extended the "what still works" paragraph to cover absent and null label fields, so a reader can tell the optional fields stayed optional. VersioningTest now fences all of it: eight required breaks, two surprising rejections, and the still-works guarantees. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ao5paex73xXvuM424Libo
#25 and #26 landed; their CHANGELOG bullets merge cleanly under the 3.0.0 heading this branch created. No conflict. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ao5paex73xXvuM424Libo
The 3.0.0 entry did NOT read correctly. Fixed in
|
| Break | Was documented under | Now also in Breaking changes |
|---|---|---|
currency is required |
### Changed |
table row |
a non-string currency throws |
### Fixed |
table row |
a non-string unit/name/source/type/formatted throws |
### Fixed |
table row |
| redirects are no longer followed | ### Security |
its own entry |
lib-curl >= 7.58.0 is now required |
nowhere | its own entry |
The libcurl floor is the one that would actually have bitten someone: it is an install-time break — Composer refuses on an older host — and it appeared in no section at all.
I also split the single table row an unparseable created_at/updated_at | a date was invented. That conflated two different changes: #18 already rejected the genuinely unparseable (not-a-date), so as written the row understated #20 rather than describing it. It is now two rows — a value PHP repaired (2026-13-45T99:99:99Z → 2027-02-18T04:40:39Z) and a relative expression (now, next friday, +1 week) — which is what actually changed.
Added the two rejections a reader would not predict and would otherwise meet on a row that looks fine: leap seconds (23:59:60) and naive timestamps read as UTC rather than host-local. Both are Karl's open judgement calls; naming them in the changelog is not the same as settling them.
Extended the "what still works" paragraph to say that absent and explicitly null label fields still mean "not provided", so a reader can tell the optional fields stayed optional.
Fenced, not just fixed
VersioningTest now asserts the section itself — eight required breaks, two surprising rejections, three still-works guarantees. Written red first against the pre-edit changelog:
1) ...@currency is required with data ('no `currency`')
"no `currency`" is not documented under "### Breaking changes".
...
7) ...@libcurl floor with data ('lib-curl >= 7.58.0')
"lib-curl >= 7.58.0" is not documented under "### Breaking changes".
8) ...@leap second
9) ...@naive timestamp timezone
Tests: 17, Assertions: 58, Failures: 9.
Green after: OK (17 tests, 58 assertions).
A break that lands in a future PR without a line in this section now fails CI rather than shipping quietly.
Verified against the actual final release state
I built main + #27 + #28 locally and read the entry that a 3.0.0 reader will get:
### Breaking changes fromArray table (7 rows), redirects, lib-curl floor, version-jump note
### Security redirects (#22), raw API paths (#17)
### Changed Breaking: currency required (#21)
### Fixed timestamps (#20), casts (#21), malformed rows (#18),
durable quota (#16), Retry-After (#16)
All four changes present. Suite on that combined state: OK (229 tests, 747 assertions).
Baselines
| Tests | Assertions | |
|---|---|---|
clean main @ 056669c (pristine clone) |
160 | 593 |
this branch (now merged with 056669c) |
177 | 651 |
simulated main + #27 + #28 |
229 | 747 |
One thing left for you, at release time
The heading reads ## 3.0.0 (unreleased). It needs its date when you tag — ## 3.0.0 (YYYY-MM-DD). VersioningTest accepts either, deliberately, so it does not block the merge; it is a release-day edit, not a blocker now.
Ordering is unchanged and still correct: #27 first, then this. Merging this first would publish a 3.0.0 whose breaking-changes table describes cast and currency behaviour that is not in the code.
🤖 Generated with Claude Code
The problem
Client::VERSIONreads'2.1.2'and the changelog entry is headed## Unreleasedwith no version at all — whilePrice::fromArray(), which ispublic, documented and callable directly, went from total to partial in
#18. It now throws
ApiExceptionfor payloads the 2.x line accepted. That is amajor, not a patch, and an integrator reading either signal would conclude
nothing in their code could break.
What this PR does
Client::VERSION→3.0.0. The change already shipped tomainlabelled as a patch; this corrects the label rather than re-releasing the
code. No
2.2.0in between, stated explicitly in the changelog.CHANGELOG gets a version heading and a
### Breaking changessection —a table of exactly what throws now that did not before, what is still
preserved, and the code a caller writes to adapt:
code, or a blank or non-stringcodecodebecame''ApiExceptionprice, or a non-numericpricepricebecame0.0ApiExceptioncreated_at/updated_atApiExceptionStill preserved: a legitimate zero or negative price, a genuinely empty
priceslist, and a row with no timestamp field (updatedAtstays null).What callers do: a caller using
Clientmethods and already catchingApiExceptionneeds no change — those methods surfaced malformed data asApiExceptionbefore. A caller invokingPrice::fromArray()on its ownpayloads must wrap it. Anyone who relied on the manufactured values (reading
$price->price === 0.0as "no data") must switch to catching. There is noopt-out by design: the manufactured values were indistinguishable from real
quotes once they left the SDK.
README gets an "Upgrading to 3.0" section making the same point where a
caller will actually see it.
tests/PublicClaimsTest.phpupdated to match.PHPStan: filed, not wired — and why
I confirmed the reviewer's count. On
origin/main@21339d43e,phpstan analyse src --level 9:[ERROR] Found 17 errors.Wiring it in here is not cheap, for a specific reason: the six
src/Price.phperrors are precisely the casts being removed in #27, andsrc/Http/CurlTransport.php:27is in the file being rewritten in #26. AddingPHPStan now means either a baseline whose line references conflict with both
open PRs, or fixing
Client.php's tenmixederrors inside a PR about versionnumbers. Either bundles work this PR should not carry.
Inventory posted to #24 with the ordering: land it after #26 and #27, when six
of the seventeen are already gone and it can go in at level 9 with no
baseline.
TDD evidence
Test written first, against unmodified
origin/main.RED (
./vendor/bin/phpunit --filter VersioningTest, source atorigin/main):Re-proved red-capable after the fix by
git checkout origin/main -- src/Client.php CHANGELOG.md:Tests: 6, Assertions: 10, Failures: 4.— then restored.GREEN (this branch):
The test is a standing fence, not a one-off: the version must be semver, its
major must be past the last BC-compatible line, the topmost changelog entry
must name a version and match
Client::VERSION, that entry must document thebreak and name what it throws, and the
User-Agentmust carry the version itclaims.
Full suite, PHP 8.5.8 / PHPUnit 13.3.3:
main@21339d43e:OK (108 tests, 478 assertions)OK (114 tests, 490 assertions)Note on ordering
#25, #26 and #27 each narrow
Price::fromArray()or the transport further.They all belong in this same 3.0.0 entry; each adds its own changelog bullet
under the section headings this PR creates. No version bump is needed on top.
🤖 Generated with Claude Code
https://claude.ai/code/session_015ao5paex73xXvuM424Libo