Reference
12 min read
Gate Kinds Reference
The gate evaluates every spec change against 33 change kinds, grouped into three lanes. ERR always blocks. WARN alerts at a configurable threshold. INFO always passes.
Source of truth: lib/sync-gate.js ERR_KINDS / WARN_KINDS / INFO_KINDS. The invariant test suite enforces that every registered kind has a detector in lib/spec-diff.js.
Quick reference
ERR -- Breaking changes (always block)
ERR-lane changes unambiguously break existing consumers. If any ERR finding is present, the gate blocks regardless of count or threshold. The only bypass is a reviewed drift exception with a named approver and a maximum 90-day TTL.
endpoint_removed
What it detects: An entire endpoint was deleted from the spec.
Example
# Before
endpoints:
- name: listUsers
method: GET
path: /users
- name: deleteUser
method: DELETE
path: /users/{id}
# After
endpoints:
- name: listUsers
method: GET
path: /users
# deleteUser removedWhy it matters: Existing consumers calling the removed endpoint receive 404 immediately -- no migration path is available at the call site.
field_removed
What it detects: A required request body field or parameter was deleted from an endpoint.
Example
# Before
models:
CreateUser:
email: string
name: string
role: string
endpoints:
- name: createUser
method: POST
path: /users
request_body: CreateUser
# After
models:
CreateUser:
email: string
name: string
# role removedWhy it matters: Any consumer that sends the removed field will receive a validation error or silent data loss; consumers that depend on the field's presence in round-trip scenarios break silently.
type_changed
What it detects: An existing field or parameter changed its primitive type incompatibly.
Example
# Before
models:
Order:
id: string
amount: string # stored as string
# After
models:
Order:
id: string
amount: integer # changed to integerWhy it matters: Every consumer that parsed the field as the original type (e.g., a string parser) will fail or corrupt data silently when it receives the new type.
auth_changed
What it detects: The authentication scheme on an endpoint changed -- scheme added, removed, switched, or required scopes changed.
Example
# Before
server:
auth:
type: bearer
# After
server:
auth:
type: api_key # scheme switchedWhy it matters: Existing auth tokens and credentials stop working immediately; every consumer must update authentication before they can call the API.
enum_value_removed
What it detects: One or more allowed values were removed from an enum-typed field.
Example
# Before
models:
Subscription:
status: string
# enum: [active, inactive, pending, cancelled]
# After
models:
Subscription:
status: string
# enum: [active, inactive]
# pending and cancelled removedWhy it matters: Consumers that match or branch on the removed enum value hit an unhandled case, causing logic failures or crashes in exhaustive switch/match blocks.
variant_removed
What it detects: A variant was removed from a oneOf/anyOf discriminated union.
Example
# Before
# PaymentMethod oneOf:
# - $ref: CreditCard
# - $ref: BankAccount
# - $ref: CryptoWallet
# After
# PaymentMethod oneOf:
# - $ref: CreditCard
# - $ref: BankAccount
# CryptoWallet variant removedWhy it matters: Consumers that branch on the removed variant hit an unhandled case or parse failure; the missing variant is treated as an unknown discriminator value.
required_param_added
What it detects: A new required parameter was added to an existing operation.
Example
# Before
endpoints:
- name: searchOrders
method: GET
path: /orders
parameters:
- name: status
in: query
# After
endpoints:
- name: searchOrders
method: GET
path: /orders
parameters:
- name: status
in: query
- name: tenant_id # new required param
in: query
required: trueWhy it matters: Existing callers that omit the new required parameter will be rejected by the server -- their requests break on the next deploy without any code change on their side.
optional_param_now_required
What it detects: An existing optional parameter was flipped to required.
Example
# Before
parameters:
- name: page
in: query
required: false # optional
# After
parameters:
- name: page
in: query
required: true # now requiredWhy it matters: Callers that previously omitted this parameter (relying on server defaults) now receive validation errors without having changed their code.
param_removed
What it detects: A parameter (path, query, header, or cookie) was removed from an endpoint.
Example
# Before
parameters:
- name: include_deleted
in: query
required: false
# After
# include_deleted parameter removed entirelyWhy it matters: Consumers that send the removed parameter receive an unrecognised field error or unexpected behavior; server-side handling of the parameter is gone.
response_field_removed
What it detects: A property was removed from a response body schema.
Example
# Before
models:
UserResponse:
id: uuid
email: string
profile_url: string
created_at: datetime
# After
models:
UserResponse:
id: uuid
email: string
# profile_url and created_at removedWhy it matters: Consumers reading the removed field receive undefined or a parse error; type-checked clients (TypeScript, Pydantic) throw at the deserialization boundary.
response_field_type_changed
What it detects: A response body field changed its type incompatibly.
Example
# Before
models:
Invoice:
id: integer # numeric ID
# After
models:
Invoice:
id: uuid # changed to UUIDWhy it matters: Downstream type-checked code that stored or compared the old type value breaks silently or throws on deserialization; database foreign keys keyed on the old type may corrupt.
response_schema_type_changed
What it detects: The overall top-level response schema type changed (e.g., object → array).
Example
# Before
endpoints:
- name: listUsers
method: GET
path: /users
responses:
- status: 200
schema: User # single object
# After
endpoints:
- name: listUsers
method: GET
path: /users
responses:
- status: 200
schema: User[] # now an arrayWhy it matters: Every consumer parser that expected the original shape (object vs. array) fails immediately; no graceful degradation path exists at the call site.
success_status_removed
What it detects: A 2xx or 3xx HTTP status code was removed from an endpoint's response set.
Example
# Before
responses:
- status: 200
schema: Order
- status: 202
description: Async processing accepted
# After
responses:
- status: 200
schema: Order
# 202 removedWhy it matters: Callers that explicitly branch on the removed status code (e.g., polling loops waiting for 202) break or enter an infinite retry when the code is no longer returned.
validation_constraints_tightened
What it detects: A validation bound was made stricter: min raised, max lowered, minLength raised, maxLength lowered, or a regex made narrower.
Example
# Before
models:
Payment:
amount: integer
# minimum: 1
# maximum: 1000000
# After
models:
Payment:
amount: integer
# minimum: 100 (raised -- stricter)
# maximum: 50000 (lowered -- stricter)Why it matters: Previously valid payloads that fall outside the tightened bounds now fail server-side validation, causing previously-working API calls to return 422 errors.
opaque_token_scheme_changed
What it detects: The token scheme type changed (e.g., bearer token → opaque API key, or JWT → session cookie).
Example
# Before
server:
auth:
type: bearer
# consumers send: Authorization: Bearer <jwt>
# After
server:
auth:
type: api_key
# consumers must send: X-Api-Key: <key>Why it matters: Every consumer holding tokens of the old scheme must re-authenticate and obtain credentials in the new format -- a credential migration, not just a config change.
error_response_shape_changed
What it detects: The schema of a 4xx or 5xx error response changed its field structure.
Example
# Before
# Error shape (before):
# { "code": 404, "message": "Not found" }
# After
# Error shape (after):
# { "error_code": "RESOURCE_NOT_FOUND", "detail": "...", "trace_id": "..." }Why it matters: Consumers that parse error responses to extract error codes, messages, or structured fields break silently or throw when field names or types change.
WARN -- Degradations (threshold-based)
WARN-lane changes are potentially breaking depending on how consumers use the API. Whether they block depends on warn_count_threshold in your project policy (default: 1 -- first WARN blocks). Set to 0 to make WARN advisory-only.
required_added
What it detects: A new required field was added to a request body model, or an existing body field was promoted from optional to required. This kind covers request body schema fields only -- for parameter-level changes (query, path, header, cookie), see required_param_added and optional_param_now_required in the ERR lane.
Example
# Before
models:
CreateOrder:
product_id: uuid
quantity: integer
# required: [product_id, quantity]
# After
models:
CreateOrder:
product_id: uuid
quantity: integer
currency: string
# required: [product_id, quantity, currency] ← currency now requiredWhy it matters: Existing consumers that don't send the new required field will receive validation errors; but this may be a deliberate schema evolution, not an oversight. Body-field required additions are WARN (not ERR) because server-side defaults can often paper over the gap, unlike required parameter additions which have no default mechanism.
field_renamed
What it detects: A property's schema reference target changed -- the diff engine treats this conservatively as a rename.
Example
# Before
models:
Shipment:
address: $ref: AddressV1
# After
models:
Shipment:
address: $ref: AddressV2 # $ref target changedWhy it matters: Consumers bound to the old schema shape lose fields or receive unexpected structure when the reference resolves to the new model.
optional_field_removed
What it detects: An optional body field disappeared from the response (detected by the live API scanner, not the diff engine).
Example
# Before
# Live API returned:
# { "id": "uuid", "email": "...", "nickname": "alice" }
# After
# Live API now returns:
# { "id": "uuid", "email": "..." }
# nickname field no longer presentWhy it matters: Consumers that read the optional field receive undefined instead of data; clients with null-safety checks may throw on the missing field depending on how optional-ness is handled.
response_field_required
What it detects: A previously-optional response field was made required by the live API -- now always present in every response.
Example
# Before
# Response could be:
# { "id": "uuid" } (profile_url absent)
# { "id": "uuid", "profile_url": "https://..." } (present)
# After
# Response now always includes:
# { "id": "uuid", "profile_url": "https://..." }
# Consumers that handled absence of profile_url may behave differentlyWhy it matters: While making a field always present sounds safe, consumers that coded for the absent case (default values, fallback logic) may produce incorrect behavior when the field is now always populated.
deprecation_violation
What it detects: A field, parameter, or endpoint marked deprecated was removed before its sunset window expired.
Example
# Before
endpoints:
- name: legacySearch
method: GET
path: /search/v1
deprecated: true # marked deprecated, sunset not yet reached
# After
# legacySearch endpoint removed -- before sunsetWhy it matters: Consumers that planned to migrate but haven't yet break immediately; the deprecation signal was a promise of continued availability until a committed date.
variant_added
What it detects: A new variant was added to a oneOf/anyOf discriminated union.
Example
# Before
# PaymentMethod oneOf:
# - $ref: CreditCard
# - $ref: BankAccount
# After
# PaymentMethod oneOf:
# - $ref: CreditCard
# - $ref: BankAccount
# - $ref: CryptoWallet ← new variant addedWhy it matters: Consumers with exhaustive match/switch logic over the union will hit an unhandled case when they receive the new variant; existing code still works for previously-known variants.
evaluator_disagreement_high
What it detects: The closure-phase evaluator council produced significantly divergent assessments of this sync run -- high spread between evaluators.
Example
# Before
# Not triggered by a spec change.
# Emitted when ICE evaluators disagree substantially
# on the IR classification for this sync run.
# After
# Gate details will show:
# "evaluator_disagreement_high"
# "spread: N points across [dimensions]"Why it matters: High evaluator spread means the gate is uncertain about this spec shape; human review is recommended before merging, even if the gate passed.
response_constraints_relaxed
What it detects: A validation bound on a response field was relaxed (max raised, maxLength raised, enum broadened).
Example
# Before
models:
Rating:
score: integer
# maximum: 5
# After
models:
Rating:
score: integer
# maximum: 100 (raised -- response constraint relaxed)Why it matters: Consumers that rely on strict server-side validation of response values (e.g., UI display logic, database column constraints) may receive values outside the range they expect.
response_enum_value_added
What it detects: A new value was added to an enum on a response field.
Example
# Before
models:
Order:
status: string
# enum: [pending, processing, complete, failed]
# After
models:
Order:
status: string
# enum: [pending, processing, complete, failed, cancelled, refunded]Why it matters: Consumers with exhaustive match/switch blocks over the response enum will hit an unhandled case when they receive a newly-added value.
INFO -- Additive changes (never block)
INFO-lane changes cannot break existing consumers. The gate always proceeds; findings are logged in the sync run for visibility.
endpoint_added
What it detects: A new endpoint appeared in the spec.
Example
# Before
endpoints:
- name: listUsers
method: GET
path: /users
# After
endpoints:
- name: listUsers
method: GET
path: /users
- name: createUser # ← new endpoint
method: POST
path: /usersWhy it matters: New endpoints cannot affect existing consumers -- callers that don't call the new endpoint are unaffected.
field_added_optional
What it detects: A new optional field or parameter was added to an endpoint.
Example
# Before
models:
UserResponse:
id: uuid
email: string
# After
models:
UserResponse:
id: uuid
email: string
avatar_url: string # new optional fieldWhy it matters: New optional fields are backward-compatible by definition -- existing consumers ignore unknown fields they don't expect.
description_changed
What it detects: Only the description text on an endpoint, field, or parameter changed -- no contract change.
Example
# Before
endpoints:
- name: listUsers
method: GET
path: /users
description: Get users
# After
endpoints:
- name: listUsers
method: GET
path: /users
description: List all active users with cursor-based paginationWhy it matters: Description text is informational only; no consumer code reads description strings at runtime.
endpoint_key_collision
What it detects: Two endpoints in the spec normalize to the same internal key (e.g., trailing-slash variants of the same path).
Example
# Before
endpoints:
- name: listUsers
path: /users
# After
endpoints:
- name: listUsers
path: /users
- name: listUsersAlt
path: /users/ # trailing slash -- normalizes to same keyWhy it matters: Advisory only -- the first occurrence wins; later duplicates are logged but do not affect gate decisions.
deprecated_flag_added
What it detects: An operation or field was marked deprecated -- a forward signal to consumers without any contract break.
Example
# Before
endpoints:
- name: legacySearch
method: GET
path: /search/v1
# After
endpoints:
- name: legacySearch
method: GET
path: /search/v1
deprecated: true # ← marked deprecatedWhy it matters: Marking something deprecated is a promise to consumers to migrate before a future removal; the contract is unchanged today.
constraints_relaxed
What it detects: A validation bound on a request field was relaxed: max raised, min lowered, maxLength raised, minLength lowered, or enum broadened.
Example
# Before
models:
CreateOrder:
quantity: integer
# minimum: 10
# maximum: 100
# After
models:
CreateOrder:
quantity: integer
# minimum: 1 (lowered -- more permissive)
# maximum: 10000 (raised -- more permissive)Why it matters: Relaxing request constraints is backward-compatible -- previously valid payloads remain valid, and consumers gain more flexibility.
optional_status_code_added
What it detects: A new success or error status code was added alongside existing ones.
Example
# Before
responses:
- status: 200
schema: Order
# After
responses:
- status: 200
schema: Order
- status: 202 # ← new status for async flow
description: Processing acceptedWhy it matters: Consumers that don't branch on the new code are unaffected; callers that handle all 2xx generically still work correctly.
metadata_changed
What it detects: Non-behavioral metadata changed: tags, examples, externalDocs, or non-behavioral extensions.
Example
# Before
endpoints:
- name: createOrder
tags: [orders]
# After
endpoints:
- name: createOrder
tags: [orders, billing] # tag updatedWhy it matters: Metadata fields have no runtime impact -- no consumer code reads tags, examples, or externalDocs at request time.
The full kind registry is enforced by the invariant test suite (tests/integration/kind-registry.test.js). New kinds require a PR updating lib/sync-gate.js and the registry test -- both must stay in sync.
Related: Drift exceptions . Handling a block . stateanchor.yaml reference . Gate classification