driftwatch

How this is decided

Severity is deterministic

Every change is classified by a fixed rule, not by a language model. The same pair of specs always produces the same severities, so a wrong call is a bug in a rule that can be found and fixed, rather than an unreproducible generation.

A model is used for one thing only: rewriting explanatory prose where the templates read thin. It never assigns severity, and it never sets the headline.

The three direction flips

Producer-side tools ask “does this break my callers?” This feed sits on the calling end, where three rules invert. They are the reason it exists.

  1. A response property becoming optional is safe for a producer and a null dereference waiting to happen for you.
  2. A new enum value in a response is additive for a producer. It breaks exhaustive switches, strict parsers and generated client enums.
  3. A removed request parameter raises no error at all. The provider ignores it, your results change, and nothing goes red.

What we are actually reading

Specifications, not behaviour. Some providers update their spec weeks after shipping, and some never do. Everything here is spec-derived, and a change that never reached the spec will never reach this feed.

Shared schemas are diffed once each, not once per endpoint that references them, and the number of operations a component reaches is shown alongside it. Following references inline instead produced 4528 “breaking” changes for a single Stripe release, the same enum reported 2319 times.

Nothing publishes itself

A change below 85 confidence never reaches the feed, and cosmetic changes never do at any confidence. Everything else is held for human review first. Precision over recall, without apology: one false breaking change costs a reader permanently.

The ruleset

RuleSeverityWhat it does to calling code
operation.removedbreakingThis verb now 405s. The path still resolves, so a health check on the URL will not catch it.
path.removedbreakingRequests to this path now 404.
request.enum.value-removedbreakingA value you may have hardcoded is now rejected.
request.param.became-requiredbreakingCalls that omit it are rejected.
request.param.removed ⇄breakingNo error is raised. The value is ignored and your results change silently, which is the hardest class of break to notice.
request.param.required-addedbreakingEvery existing call that omits it is rejected.
request.property.became-requiredbreakingPartial payloads are rejected.
request.property.removed ⇄breakingUsually ignored rather than rejected, so the call succeeds and does something subtly different.
request.property.type-changedbreakingSerialization mismatch. Integer-to-string moves are the usual cause and the usual outage.
response.enum.constraint-removed ⇄breakingThe documented set of values is gone, so anything can appear. Every exhaustive switch and generated enum over this field is now incomplete.
response.enum.value-added ⇄breakingProducer-side tools call this additive. It breaks exhaustive switches, strict parsers and generated client enums.
response.property.became-optional ⇄breakingProducer-side tools call this non-breaking. For a consumer it is a null dereference on an unknown future date.
response.property.removedbreakingReads of this field return undefined, and the failure surfaces wherever that value is used rather than at the call.
response.property.type-changedbreakingParse failure, or silent coercion depending on your client.
security.scheme-changedbreakingThe integration needs reworking, not patching.
security.scope-addedbreakingExisting tokens 403. This needs user re-consent, not a code change, so it cannot be fixed by deploying.
operation.deprecateddeprecationStill works today. This is the earliest warning you will get before it is removed.
default.value-changedbehavioralBehavior shifts for every call that omits the field.
pagination.default-changedbehavioralSilent truncation. Code that does not paginate keeps working and quietly returns a different slice of the data.
response.property.now-always-nullbehavioralNothing throws, since the field could already be null, but the value you read here is gone. Often a spec-generator artifact, so it is held for a person to check.
response.status-code.addedbehavioralAn unhandled branch in your error handling.
response.status-code.removedbehavioralDead branch, or an undocumented response you still receive.
schema.composition-changedbehavioralThe set of shapes this value can take changed. Worth a look, but the structural diff cannot say precisely how.
schema.ref-retargetedbehavioralOften a rename with an identical shape, sometimes a genuine retype. The structural diff cannot tell which, so this is held below the publish threshold on purpose.
security.requirement-removedbehavioralThe operation no longer demands this credential.
operation.addedadditiveNo action needed.
operation.undeprecatedadditiveThe operation is supported again.
path.addedadditiveNo action needed.
request.enum.constraint-removedadditiveA relaxation, not a break. Everything you could send before still works, and more is accepted now.
request.param.optional-addedadditiveNo action needed.
request.property.addedadditiveNo action needed unless it is required.
request.property.became-nullableadditiveA relaxation. Existing payloads are unaffected.
response.property.addedadditiveNo action needed unless you validate responses against a closed schema.
response.property.type-narrowedadditiveA tighter guarantee, not a break. Every value you can receive now was already possible, so existing handling still covers it; a null check here may now be dead code.
cosmetic.description-changedcosmeticNone.
cosmetic.schema-renamedcosmeticThe shape is identical. Only the name changed.
request.param.unlistedcosmeticThe same parameter still exists as a shared component, or on other operations. This is a documentation drop, not a withdrawn argument.

⇄ marks a rule producer-side tools classify the other way.