With a sufficient number of users of an API, it does not matter what you promise in the contract: all observable behaviors of your system will be depended on by somebody.
Every undocumented behavior — response ordering, timing, error message format, type-check side effects — becomes a de facto contract once someone depends on it. Changing any observable behavior risks breaking consumers who never read the docs because the behavior was stable enough to rely on.
## Mechanism
1. System exposes behavior B (documented or not)
2. Consumer observes B and builds on it
3. Producer changes B (believing it was an implementation detail)
4. Consumer breaks silently — no contract was violated, but the system fails
## Implications for SRE
- **API versioning doesn't prevent it.** Users depend on undocumented error formats, response field ordering, and timing behavior within a version.
- **Integration tests at boundaries are essential.** Unit tests on each side pass. The implicit behavioral contract only breaks at the seam.
- **"Safe" refactors break production.** Changing an internal implementation detail propagates through type checks and guards that depended on the old behavior.
- **Aggressive API versioning is the only defense.** Once behavior is observed, it's depended on. Freeze it behind a version number.
## Concrete Example: Stripe Parameter Bug (chaos-app, Sep 2025)
ActiveMerchant's `add_customer` guard (since 2013):
```ruby
post[:customer] = options[:customer] if options[:customer] && !payment.respond_to?(:number)
```
- Old checkout: payment source = `Spree::CreditCard` (responds to `:number`) → guard blocks email → works
- Rails 6.0 checkout: payment source = string token (doesn't respond to `:number`) → guard lets email through → Stripe rejects every transaction
Nobody explicitly depended on the credit card object keeping the guard active. But the system implicitly depended on it for 10+ years. A type change three layers deep flipped a guard nobody knew existed.
## Key Distinction
Hyrum's Law is not about bugs in the API. It's about **correct behavior that consumers treat as a guarantee**. The `respond_to?(:number)` guard was working exactly as designed. The problem was that the upstream system changed the type of object it passed through, and the guard's correct behavior produced a different (now harmful) outcome.