Repository navigation
feat(standard-tests): check spec backward compatibility against the published version - #1220
Merged
Anatolii Yatsuk (tolik0) merged 8 commits intoOct 13, 2026
Merged
Conversation
…ublished version Add `test_docker_image_spec_backward_compatibility` to the Docker test suite. It fetches the published `latest` spec of each registry (OSS and Cloud), runs `spec` in the image under test with the matching deployment-mode environment, and fails when the new spec rejects configs that the published version accepted: removed or newly required properties, added or tightened constraints, narrowed types and enums, removed sync modes, removed oneOf branches or moved OAuth output paths. Breaking findings are waived when metadata.yaml declares a breaking change for a version after the published one, or when `disable_for_version` in the `spec` section of acceptance-test-config.yml equals the published version, so the waiver expires with the next release. The failure message points at the breaking-change process and the migration-guide requirement. The comparison rules are ported from the ops regression spec comparator, scoped to the parts of a spec that existing connections depend on. OAuth consent-flow templates only affect new authorizations and are compatible. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
👋 Greetings, Airbyte Team Member!Here are some helpful tips and reminders for your convenience. 💡 Show Tips and TricksTesting This CDK VersionYou can test this version of the CDK using the following: # Run the CLI from this branch:
uvx 'git+https://github.com/airbytehq/airbyte-python-cdk.git@tolik0/cdk/spec-backward-compatibility#egg=airbyte-python-cdk[dev]' --help
# Update a connector to use the CDK from this branch ref:
cd airbyte-integrations/connectors/source-example
poe use-cdk-branch tolik0/cdk/spec-backward-compatibilityPR Slash CommandsAirbyte Maintainers can execute the following slash commands on your PR:
|
…hanges that break configs Only JSON Schema validation keywords and the protocol keys that locate values in a saved config can now be breaking. Every other key of a schema node is an annotation, so changing or removing `airbyte_hidden`, `multiline`, `$schema`, `order`, `group`, vendor keys and the like is compatible. `airbyte_secret` is compared by direction: marking a field secret, or rewriting the string "true" as a boolean (the destination-convex fix needed by the spec lint), is compatible, while unmarking a secret stays breaking. Validation keywords the comparator does not model (`not`, `if`/`then`/`else`, `dependencies`, ...) are breaking when added or changed and compatible when removed. A keyword added or removed at its default value (`additionalProperties: true`, `uniqueItems: false`, `minLength: 0`, `required: []`, ...) is no longer reported as narrowing, and a value keyword moved to its default is a relaxation. Emptying `properties` or `items` still reports the removed fields. `integer` widened to `number` is compatible. oneOf/anyOf branches are paired by a ranked match instead of a single key: identical branches first, then shared discriminating consts, the same title, shared fields and types, and position. Adding an optional discriminator, renaming the title of a branch without one, or two branches sharing a discriminator no longer read as removed branches, while a renamed discriminator is still one branch removed and one added. A breaking change declared for 2.0.0 now waives an image tagged 2.0.0-rc.N. Passed and waived comparisons print their compatible (and waived) changes, capped at 20 per list. Registry requests use 5 s connect and 10 s read timeouts with two retries, so an unreachable registry fails in about 30 seconds instead of minutes, and the failure explains how to deselect the test. Retries are tested against a local HTTP server. `Retry` is imported through `requests.adapters`, which fixes the Deptry DEP003 on the transitive `urllib3` import. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 task done
1 task done
Daryna Ishchenko (darynaishchenko)
requested changes
Oct 9, 2026
Daryna Ishchenko (darynaishchenko)
left a comment
Contributor
There was a problem hiding this comment.
Verdict: fix before merge — P0: 0 · P1: 2 · P2: 4 · P3: 6. Inline comments carry F1–F12.
Breaking change: BREAKING under criterion 7 only — a new mandatory standard test that ~619 non-JVM monorepo connectors adopt automatically on the next CDK release, because connector CI installs airbyte-cdk[dev] unpinned. No runtime, API, manifest, state or packaging break. This needs an unmissable call-out and a rollout plan (F6), not a major bump.
Done well
- The comparator models JSON Schema semantics rather than diffing text: keyword defaults, bound direction,
typeas a set with theinteger → numberwidening, andoneOfbranches matched by discriminator. Nine tightening probes all landed on the correct side. - The waivers are self-expiring:
disable_for_versionmust equal the publishedlatest, and the declared-breakingChangesrange handles release candidates and progressive rollouts. All 42 existingdisable_for_versionentries are inert today, so nothing is silently waived at rollout. - 156 hermetic unit tests in under 6 s (local HTTP server for the retry policy, no real docker or network); 7 of 12 mutation probes caught. The failure message names the field, the three resolution paths and the exact waiver YAML.
Rollout / risk
- Fleet sweep: every manifest-only connector's
manifest.yamlspec onorigin/mastercompared against the published OSS and Cloudlatestwith this PR'scompare_specs— 516 connectors, 0 breaking. 12 manifest-only connectors rebuilt on the current SDM base emitted specs identical to the registry. The 105 Python connectors are covered only by the author's six end-to-end runs; the two confirmed day-one reds are the F1 crashes. - The Docker half of the new test never executes in CDK CI: the pokeapi fixture (0.3.24) is skipped by the older-version rule after a live registry fetch, and the six
Check: <connector>jobs run connector unit tests, notimage test. Consider bumping the fixture so the real path runs once per CDK PR. - CI:
Check: destination-motherduckis red here but also on #1217, #1218, #1219, #1221, #1223 and #1224 — infrastructure, not this diff.Skipped Check: source-google-drivedid not run (bucket untouched). - #1219 adds a different
specrunner at the same place indocker_base.py; expect a conflict with whichever lands second (F10).
Rename `backward_compatibility.py` to `_spec_compatibility.py`, like its siblings `_job_runner.py`, so pdoc does not publish the comparator, the registry helpers and their constants as supported CDK API. The user-facing documentation stays in the test docstring, which pdoc renders through `DockerConnectorTestSuite`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nProperties `oneOf` accepts a config only if exactly one branch matches it, so a branch that also matches the configs of another one rejects them. An added `oneOf` branch is now compatible only if it provably rejects the configs of every previous branch: it requires a field they do not declare, or pins a discriminator to a different value. A matched branch that loses that proof, for example by dropping its discriminating `const`, is breaking. `anyOf` is unchanged, as an added branch only widens it. An added `patternProperties`, or a pattern added to one, is breaking: it applies to every field whose name matches, so it can reject values that saved configs already set. Equality is now type-strict throughout the comparison, so `enum [1]` to `[true]` and `const 0` to `false` are reported. `additionalProperties` or `items` relaxed from `false` to a schema, and a `multipleOf` relaxed to a divisor of the old value, are compatible. Over 34,005 consecutive OSS and 31,906 Cloud release pairs, one more pair is flagged (a `oneOf` branch that stopped requiring the field that set it apart). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…try outages and legacy configs - Skip, with a warning, when the connector registry cannot be reached after retries (connection, proxy and timeout errors, or retries exhausted on 5xx and 429). A registry outage no longer fails every connector PR and the CDK's own CI, and the old advice to deselect with `-k` was not reachable through `airbyte-cdk image test`. A client error status, invalid JSON or an entry without `spec` or `dockerImageTag` still fails, naming the URL. - Read `disable_for_version` from the legacy list layout of the `spec` section too, and treat an empty or malformed acceptance-test-config.yml as having no waivers. The file is only read once a comparison is breaking. - When `spec` exits non-zero or emits no SPEC message, report the exit code, the connector's last error TRACE message, its last log lines and its stderr, instead of a bare CalledProcessError. - Report the compatible and waived changes as a warning rather than with `print`, so they appear in the warnings summary of a CI run. - Drop `allowed_methods=` from the retry policy. It needs urllib3 1.26, which the CDK does not require, and GET is retried by default. - Test registries that publish different versions, a waiver that covers only one registry, the image build path and the https retry mount. Turn a skip of the test under test into an error in the unit tests, so a test expecting a failure cannot pass on a skip. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Connector CI installs the CDK CLI unpinned, so every connector that runs the Docker suite is held to the test on the next release without opting in. The migration note says what fails, when the test skips, and the two waivers, including how a connector without acceptance-test-config.yml waives it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fixture's dockerImageTag (0.3.24) was older than the published source-pokeapi (0.3.74), so test_docker_image_spec_backward_compatibility skipped under the older-version rule and its Docker path never ran in CDK CI. Set the tag to the published version so the test builds the image, runs `spec` in both deployment modes and compares it against the registry. Align the pokemon_name enum with the published spec (`nidoran-f`, `nidoran-m`); the old spellings drop two published values, which the comparator rightly flags as breaking. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merged
2 of 6 tasks
Anatolii Yatsuk (tolik0)
requested a review
from Daryna Ishchenko (darynaishchenko)
October 12, 2026 13:31
3 of 7 tasks
Daryna Ishchenko (darynaishchenko)
approved these changes
Oct 13, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Anatolii Yatsuk (tolik0)
deleted the
tolik0/cdk/spec-backward-compatibility
branch
October 13, 2026 16:33
Anatolii Yatsuk (tolik0)
added a commit
that referenced
this pull request
Oct 13, 2026
Combines #1217, #1219 and #1220 with this branch: - test_discover keeps the section gate of this branch and runs the catalog checks from main; an empty catalog now fails the enforced 'catalog has streams' check, so its test expects that assertion. - test_check runs the secret leak check before asserting the check outcome. - cdk-migrations.md lists every section under one 7.36.0 heading. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds
test_docker_image_spec_backward_compatibilitytoDockerConnectorTestSuite. The test fails when a connector's spec rejects configs that the published version accepted. Users' saved configs are validated against the new spec when a release rolls out, so this kind of change breaks existing connections at upgrade time.This is P3.1 of the CAT → FAST coverage epic (airbytehq/airbyte-internal-issues#17144, under #17129). It replaces CAT's
test_backward_compatibilityfor specs. The discovery/catalog half is P3.2 and is not in this PR.How it works
The test fetches the published
latestspec from the public registry, once per registry:https://connectors.airbyte.com/files/metadata/<dockerRepository>/latest/{oss,cloud}.json. Registrylatestis what most users run. During a progressive rollout it stays on the previous GA release, so a patch on top of a release candidate is compared against the version users actually have.The test runs
specin the image under test once per registry. It sets the same environment the registry publisher sets (DEPLOYMENT_MODEplusAIRBYTE_EDITION), so cloud-only spec differences are compared like for like. Both sides are compared as raw JSON, not through the protocol model, because the registry stores the raw output.compare_specsclassifies every difference as breaking or compatible. Only JSON Schema validation keywords and the protocol keys that locate values in a saved config (path_in_connector_config,path_in_oauth_response,predicate_key/predicate_value, the OAuth input and output schemas) can be breaking. Every other key is an annotation (title,description,order,group,airbyte_hidden,multiline,$schema,x-*, …), and changing it is compatible.requiredlist.enum,pattern,const,format,maxLength,additionalProperties: false, …) are breaking.typesets, narrowedenums and removed destination sync modes are breaking.airbyte_secretbecoming false or removed) is breaking.not,if/then/else,dependencies, …) are breaking when added or changed. None appears in the fleet today.nulladded totype,integerwidened tonumber, new optional properties and newly secret fields are compatible.airbyte_secret: "true"rewritten astrueis compatible too.additionalProperties: true,uniqueItems: false,minLength: 0,required: [], …) is compatible, because it accepts the same configs.oneOf, a config must match exactly one branch. An addedoneOfbranch is compatible only if it provably rejects the configs of every previous branch: it requires a field they do not declare, or pins a discriminator to a different value (required by one of the two, or left out only by configs that another branch already matched). A matched branch that loses that proof, for example by dropping its discriminatingconst, is breaking. UnderanyOf, an added branch only widens and is compatible.patternProperties, or a pattern added to one, is breaking: it applies to every field whose name matches, so it can reject values that saved configs already set.enum [1]becoming[true], orconst 0becomingfalse, is a change.additionalPropertiesoritemsrelaxed fromfalseto a schema, and amultipleOfrelaxed to a divisor of the old value, are compatible.oneOf/anyOfbranches are matched by a ranked match: identical branches first, then shared discriminating consts, then the same title, then shared fields and types, then position. Reordering auth methods, adding an optional discriminator, or renaming the title of a branch is therefore not a change. Two branches that disagree on a discriminator are never paired, so a renamedauth_typeconst is still one branch removed and one added.The rules are ported from the spec comparator in the ops regression tests (airbyte-ops-mcp#1201). Because this test blocks merges, it differs from that comparator in two ways:
connectionSpecification,advanced_authandsupported_destination_sync_modesare compared. Nothing else in a spec affects existing connections.advanced_auth.oauth_config_specification.oauth_connector_input_specification(declarative OAuth consent URLs, token URLs, scopes,extract_output) is compatible. It only affects new authorizations. In the fleet history it was the single largest source of findings that cannot break a saved config.When the test passes, fails or is skipped
releases.breakingChangesentry inmetadata.yamlfor a version in(published latest, dockerImageTag]2.0.0-rc.1counts as2.0.0.backward_compatibility_tests_config.disable_for_versionin thespecsection ofacceptance-test-config.ymlequal to the publishedlatestversionspec.testslayout and the legacy list layout are read. An empty or malformed file has no waivers, and the file is only read once a comparison is breaking.dockerImageTagolder than the published version (a branch that has not merged the latest release)specordockerImageTagspecexits non-zero or emits no SPEC messageOn the waivers:
disable_for_versionis the same key CAT honored, so existing configs keep working. A connector withoutacceptance-test-config.yml, such as the Java connectors that the base-image workflow rebuilds without a version bump, can add a file that holds only this entry. Because it must equal the publishedlatestversion, the waiver expires on its own once the next release ships. A stale value cannot silently disable the check. This is the documented escape for config migrations and false positives such as a widened regex.latest. A patch on top of a major that is still rolling out is waived too.The failure message tells the author to do one of the following:
releases.breakingChanges, and write a migration guide. The message links the breaking-changes docs.disable_for_version. The message gives the exact YAML.Acceptance criteria (#17144)
@pytest.mark.image_tests), so it runs underairbyte-cdk image test. That is the only path Python connectors use in monorepo CI, and manifest-only connectors run it too. It reaches every connector on the next CDK release, because monorepo CI installsairbyte-cdk[dev]unpinned. P0.1 and P0.7 are not prerequisites for this reason.disable_for_version(or a replacement escape) is honored. Done, plus the declared-breakingChangeswaiver. See above.cdk-migrations.md. The comparator module is private (_spec_compatibility.py), so its helpers are not published as CDK API. A follow-up docs PR in the monorepo should updateconnector-acceptance-tests-reference.md.validate_previous_configsis ported or explicitly dropped. Dropped. CAT generated configs from the previous spec withhypothesis-jsonschemaand validated them against the new one. Porting it would add two heavy dependencies to the CDK and make a merge-blocking test nondeterministic. The structural rules here catch the same classes of breakage deterministically, and the failure message names the exact field. CAT'sprevious_connector_versionis not ported either: one connector sets it, and that entry also setsdisable_for_version.Validation
Unit tests (
unit_tests/test/test_spec_compatibility.py, 213 tests) cover:airbyte_secretdirection, keywords at their default value,integer→number, branch matching, overlappingoneOfbranches,patternPropertiesand type-strict equalitydisable_for_versionparsing, in both layouts and for empty or malformed configsrequests_mock, plus the retry policy against a local HTTP server (recovery, give-up after 2 retries, no retry on 4xx, fast failure on a closed port) and on both schemesdocker runcommand per deployment mode, and the diagnostics of a failingspecA skip of the test under test is turned into an error in these tests, so a test that expects a failure cannot pass on a skip. The reviewer's mutation probes (any newer registry skipping all registries, skip before fail, no https retry mount) each fail at least one test.
End to end against published images. I ran the real test (registry fetch plus
docker run … specper mode) with--connector-imageset to the publishedlatestof six connectors. All six pass:For source-file and source-oracle, the spec emitted in Cloud mode differs from the one emitted in OSS mode. Compared against the published OSS spec, it reports the expected Cloud-only removals (the local-file provider, and
encryptionbecoming required). This confirms that the environment variables reach both the legacy Python CDK and the Java CDK.CDK's own fixture. The pokeapi fixture in
unit_tests/resources(source_pokeapi_w_components_py) runs the Docker suite in this repo's Pytest jobs. It was an older copy (0.3.24) of source-pokeapi, which is published at 0.3.74, so the test was skipped by the older-version rule and its Docker path never ran in CDK CI. This PR bumps the fixture'sdockerImageTagto 0.3.74 and aligns itspokemon_nameenum with the published spec (nidoran-f,nidoran-m; the old spellings dropped two published values). The test now builds the fixture image, runsspecin both deployment modes and compares it against the registry on every CDK PR. It passes, and with the old enum spellings it fails on exactly those two values. Once source-pokeapi publishes a version past 0.3.74, the fixture falls back to the skip until it is bumped again.Fleet dry run. I ran the comparator over every connector in the monorepo with a published registry entry: 619 connectors, and every consecutive pair of published GA versions (4,541 pairs).
latest. None of them has a breaking finding, so releasing this does not turn any connector red by itself.1 was a declared major: facebook-marketing 6.0.0 narrowed an enum. It would have been waived.
16 were undeclared:
enum), zendesk-support 5.6.0 (num_workersminimum 1 → 2), sentry 1.0.10 (newpattern/minLength).oneOfshape change: destination-google-sheets 0.3.0.disable_for_versionis the escape.Some of these may have shipped with a config migration. Under this test they would have needed
disable_for_version.Re-run after the review fixes, over every non-prerelease, non-yanked version in each connector's
versions.jsonthat has a per-version registry entry. This is a wider history than the 4,541 pairs above:No pair went from compatible to breaking. The pairs that are no longer flagged break down as follows:
additionalProperties: trueadded (the manifest-only migration shape)$schemaeditsairbyte_hiddenremovedmaxkeyoneOfbranch mismatchfacebook-marketing 6.0.0, the pattern rewrites (coingecko-coins, ramp, shopify) and zendesk-support 5.6.0 are still flagged. The fixed comparator is stricter only on keywords that occur 0 times in current specs, so the impact at rollout is unchanged: 0 connectors red.
Re-run after the second review round (overlapping
oneOfbranches,patternProperties, type-strict equality), over the same history: OSS 266 of 34,005 pairs flagged (was 265), Cloud 247 of 31,906 (was 246). The one new pair is aoneOfbranch that stopped requiring the field that set it apart from the other branch, so configs of the other branch without the discriminator match both. No pair went from breaking to compatible. At rollout, all 516 manifest-only connectors onmastercompared against the published OSS and Cloudlatest(1,004 connector × registry pairs): 0 breaking.Rollout
There is no warn-only release: the test blocks connector PRs from the first CDK release that ships it.
masteris red. The two connectors with the legacy list layout ofacceptance_tests.spec(source-pardot, source-netsuite) no longer crash the test.disable_for_versionin the connector, and the comparator fixed here.Coordination with #1219
#1219 adds
_run_docker_spec(connector_image, *, connector_name)beside_run_spec_in_imageindocker_base.py, so whichever lands second conflicts in the import block and has twospecrunners. The second one to land should fold both into one helper keyed by(image, deployment mode)that returns the raw spec, with #1219 parsing it intoConnectorSpecification.🤖 Generated with Claude Code