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.