packages/kbn-api-contracts/README.md
Detects breaking changes in Kibana's public REST APIs that affect the Terraform provider, by comparing OpenAPI specs between the PR branch and the base branch using oasdiff.
This package runs in CI on every PR. It compares the current branch's OAS files against the base branch (e.g. main) to detect breaking API changes, then filters to only those affecting Terraform provider APIs.
Flow:
git show base OAS → oasdiff diff → parse → filter to TF APIs → apply allowlist → report → notify owners
Key components:
src/diff/ - Breaking change detection via oasdiff
run_oasdiff.ts - Shells out to oasdiff diff with two OAS filesparse_oasdiff.ts - Converts oasdiff JSON output to BreakingChange[]breaking_rules.ts - Allowlist filteringsrc/terraform/ - Terraform impact analysis and ownership
check_terraform_impact.ts - Matches breaking changes against TF provider APIs, carries ownersload_terraform_apis.ts - Loads TF API inventory (including owners) from terraform_provider_apis.yamlsrc/report/ - Error formatting and user guidance
format_failure.ts - Generates actionable error reportslinks.ts - Documentation and support linkssrc/allowlist/ - Escape hatch for approved breaking changes
load_allowlist.ts - Loads and validates allowlist.jsonsrc/input/ - OAS file loading (exported as public API)
load_oas.ts - Reads and validates OpenAPI spec YAML filesscripts/ - CLI entry point
check_contracts.ts - Orchestrates the full pipelineoasdiff detects these as breaking:
| Change Type | oasdiff ID(s) | Example |
|---|---|---|
| Path removed | api-path-removed-without-deprecation | DELETE /api/spaces/space removed entirely |
| Method removed | api-removed-without-deprecation, api-removed-before-sunset | POST removed from /api/fleet/agents |
| Request property removed | request-property-removed ⚠️ | Request body field name removed |
| Parameter removed | request-parameter-removed ⚠️ | Query param filter removed from GET /api/agents |
| Response property removed | response-required-property-removed, response-optional-property-removed ⚠️ | Response field id removed from 200 response |
| Required property added | new-required-request-property | New required email field on request body |
| Optional made required | request-parameter-became-required | filter query param becomes required |
| Type changed | response-property-type-changed | id changed from string to number |
| Request body tightened | kbn:request-additional-properties-tightened | Request body schema gains additionalProperties: false (clients sending unknown keys now receive 400) |
⚠️ oasdiff classifies these as warnings, but they are promoted to blocking here because Terraform provider configurations depend on these fields.
For approved breaking changes, add entries to allowlist.json. Always prefer the granular form below — it scopes suppression to one specific breaking change instead of muting everything on the endpoint.
Use oasdiffId together with source to suppress exactly one breaking change. These fields are AND'd with path and method: the entry only matches changes for which all four fields agree.
oasdiffId — matches the oasdiff rule ID (e.g. request-property-removed, kbn:request-additional-properties-tightened). See the Breaking Change Rules table for known IDs.source — matches the JSON pointer / source location reported by oasdiff (e.g. /components/schemas/Output/properties/name).{
"path": "/api/fleet/outputs",
"method": "post",
"reason": "Approved removal of deprecated 'name' field from request body",
"approvedBy": "@elastic/terraform-provider",
"oasdiffId": "request-property-removed",
"source": "/components/schemas/Output/properties/name"
}
Example targeting the new request-body tightening rule:
{
"path": "/api/data_views/data_view",
"method": "post",
"reason": "Intentional tightening — coordinated with @elastic/terraform-provider",
"approvedBy": "@elastic/kibana-data-discovery",
"oasdiffId": "kbn:request-additional-properties-tightened",
"source": "/components/schemas/Data_views_create_data_view_request_object"
}
Required fields: path, method, reason, approvedBy, oasdiffId, source (the last two only required for granular suppression).
Optional fields: prUrl, expiresAt.
Omitting oasdiffId and source makes the entry suppress every breaking change for that (path, method). This is dangerous: a coarse entry approved today silently swallows any unrelated tightening, removal, or type change that lands on the same endpoint in the future. Reach for it only when several distinct, approved changes ship together and a single granular entry per change is impractical.
{
"entries": [
{
"path": "/api/saved_objects/{type}/{id}",
"method": "delete",
"reason": "Intentional removal as part of saved objects migration",
"approvedBy": "elastic/kibana-core",
"prUrl": "https://github.com/elastic/kibana/pull/12345",
"expiresAt": "2026-12-31"
}
]
}
Each entry in terraform_provider_apis.yaml has an owners field listing the GitHub team handle(s) responsible for that API (derived from .github/CODEOWNERS):
- path: /api/spaces/space
methods: [GET, POST]
resource: elasticstack_kibana_space
owners: ['@elastic/kibana-security']
When adding a new Terraform-consumed API, always set owners to the team that owns the plugin/package registering the route.
When the contracts check fails (i.e. there are non-allowlisted TF-impacting breaking changes), CI automatically posts (or updates) a PR comment that @mentions the owning teams for the affected endpoints. No comment is posted when the check passes. This ensures the right people are notified and can decide whether to fix the change or add an approved allowlist entry.
Runs via .buildkite/scripts/steps/checks/api_contracts.sh on every PR. Compares against $BUILDKITE_PULL_REQUEST_BASE_BRANCH (defaults to main).
# Check stack contracts against main
node scripts/check_api_contracts.js \
--distribution stack \
--specPath oas_docs/output/kibana.yaml
# Check serverless contracts against a specific commit
node scripts/check_api_contracts.js \
--distribution serverless \
--specPath oas_docs/output/kibana.serverless.yaml \
--mergeBase <commit-sha>
Flags:
--distribution (required) - stack or serverless--specPath - Path to current OAS file (auto-detected from distribution)--baseBranch - Branch to compare against (default: main)--mergeBase - Merge base commit SHA (used in CI, skips remote resolution)--allowlistPath - Override allowlist path--terraformApisPath - Override TF API inventory path--reportPath - Write a JSON impact report to this path (used by CI for PR notifications)Prerequisites: oasdiff must be installed and available in PATH (or set OASDIFF_BIN):
brew install oasdiff
# or: curl -fsSL https://raw.githubusercontent.com/oasdiff/oasdiff/main/install.sh | sh
When the check fails, CI posts a PR comment tagging the owning teams for the affected endpoints.
@elastic/terraform-providerThe base branch OAS file isn't available. This happens on:
oas_docs/output/ is committedThe script auto-detects the elastic/kibana remote (falls back to origin) and tries git fetch {remote} {branch} --depth=1 as a fallback. If --mergeBase is provided, remote resolution is skipped entirely.
Install oasdiff locally or set OASDIFF_BIN to the binary path. In CI, oasdiff is pre-installed on the agent image.
yarn kbn bootstrap
yarn test:type_check --project packages/kbn-api-contracts/tsconfig.json
# Unit tests
yarn test:jest packages/kbn-api-contracts
# Type check
yarn test:type_check --project packages/kbn-api-contracts/tsconfig.json