Skip to content

feat(standard-tests): lint spec structure, form rendering and OAuth config paths - #1227

Draft
Anatolii Yatsuk (tolik0) wants to merge 4 commits into
mainfrom
tolik0/cdk/spec-structural-lints
Draft

Anatolii Yatsuk (tolik0) wants to merge 4 commits into
mainfrom
tolik0/cdk/spec-structural-lints

Conversation

@tolik0

@tolik0 Anatolii Yatsuk (tolik0) commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Release after the connector fixes. 18 connectors fail an enforced lint today: 14 Python community, 1 Python archived, 1 manifest-only certified, and 2 certified Java connectors that airbyte's Dockerfile Tests workflow runs through airbyte-cdk image test. 16 are fixed in the PRs below. 2 remain known failures: destination-timeplus (archived) and source-mssql (Java, Dockerfile Tests only). Monorepo CI installs the airbyte-cdk CLI unpinned, so the enforced lints reach every connector on this release. Merge these monorepo PRs first:

Not a release blocker: airbytehq/airbyte#88928 fix(source-tplcentral): set additionalProperties: true in the spec (branch tolik0/spec-lint-tplcentral-additional-properties). source-tplcentral's legacy top-level tests: config fails CDK collection, so the new lint never runs on it until that config is converted.

What

Resolves airbytehq/airbyte-internal-issues#17136 (P1.3 of the CAT to FAST coverage epic). It ports CAT's structural spec checks into the spec tests. The tests are test_spec (in-process) and test_docker_image_build_and_spec (Docker). The new lints run next to the airbyte_secret marking lint from #1219. Each lint reports every problem with the JSON pointer of where it sits in the spec, and never prints a value.

Enforced lints (fail the test)

Lint Ported from Rule
valid JSON Schema new; CAT only caught this through test_config_match_spec connectionSpecification validates against the meta-schema of its $schema draft (draft 7 by default)
refs resolved test_defined_refs_exist_in_json_spec_file No $ref anywhere in the emitted spec, advanced_auth included
additionalProperties is true test_additional_properties_is_true additionalProperties is true wherever it is set
oneOf usage test_oneof_usage The schema is type: object, every variant has properties, and the variants share exactly one discriminator (see below)
enum usage test_enum_usage No duplicate enum values
array items test_array_type items is a single schema of type object, string, number or integer (one type, not a list), or has an enum
property types test_property_type_is_not_array A list type is one type, or one type followed by null
display_type test_display_type Only on a field with oneOf, and only dropdown or radio
duplicate order test_duplicate_order order is unique within one object and group
nested group test_nested_group group is set on top-level properties only
OAuth config paths test_oauth_flow_parameters predicate_key and every path_in_connector_config name a property of connectionSpecification

oneOf discriminator rules:

  • The discriminator is a property that every variant sets as const.
  • Failing that, it is a property that every variant sets as a single-item enum with that item as its default. The webapp's schemaToFormBlock treats that as a const.
  • A property with a multi-item enum, such as compression_codec, is not a candidate. CAT treated it as one, which is a false positive on destination-aws-datalake.

Advisory lints (SpecLintWarning)

forbidden keywords (test_forbidden_complex_types) and object not empty (test_object_not_empty) report their problems as the new SpecLintWarning, exported from airbyte_cdk.test.standard_tests. Their remaining violators cannot be fixed in a backward-compatible way:

  • source-facebook-marketing (certified): pydantic emits a single-item allOf for an enum-typed field.
  • destination-ragie: pydantic v2 emits anyOf: [X, null] for its Optional fields.
  • source-smoke-test: the spec is built in PyAirbyte, and its free-form json_schema and records objects cannot gain properties.

Inlining the single-item allOf, or rewriting anyOf: [X, null] as X with type: [<X's type>, "null"], accepts exactly the same configs. Dropping the null branch instead would reject saved configs that set the field to null. Even the equivalent rewrite fails the spec backward-compatibility check (#1220), which reports type/enum added and allOf/anyOf removed and calls the fix breaking. Follow-up: teach the comparator that those forms are equivalent, fix the two connectors, then enforce. Running pytest with -W error::airbyte_cdk.test.standard_tests.SpecLintWarning turns the warnings into failures now.

A keyword value of an unexpected type, such as an items.type list, a list display_type or a non-string $schema, is reported at its pointer like any other problem. A lint that still raises on an unexpected spec is reported as a problem of that lint ("The lint could not run: ."). It is never raised, so an advisory lint cannot fail a test by crashing.

CAT checks not ported, and why

CAT check Decision Reason
test_date_pattern Dropped CAT only warned. It asks for MM-DD-YYYY on format: date fields, which contradicts the date picker and the docs (YYYY-MM-DD). Adding a pattern narrows the accepted configs. 264 connectors would flag.
test_date_format Handed to airbyte-ops local connector qa 1 violator (source-news-api). Adding format narrows configs, so the connector needs a waiver.
test_oauth_is_default_method Handed to airbyte-ops local connector qa 1 violator (source-jira, certified), which offers OAuth third on purpose. CAT needed the auth_default_method bypass for this, and the standard tests have no per-connector exception.
test_image_labels Dropped airbyte-cdk image build sets io.airbyte.name and io.airbyte.version from metadata.yaml, so a connector cannot get them wrong
test_image_environment_variables Dropped The shared Dockerfile.python-connector and Dockerfile.manifest-only-connector set AIRBYTE_ENTRYPOINT equal to ENTRYPOINT

Fleet dry run

The lints were run on the committed specs at airbyte master 1e22e3c689c:

  • Manifest-only connectors: the spec from ConcurrentDeclarativeSource.spec().
  • Python connectors: spec.json, spec.yaml or the manifest spec block, or the registry spec when the spec is built in code.

The registry specs were scanned as a cross-check, and the results agree. Java connectors run no CDK standard tests in connector CI, with one exception: airbyte's Dockerfile Tests workflow (docker-connector-base-image-tests.yml) runs airbyte-cdk image test, with an unpinned CDK, on 4 Java connectors when a pull request changes the Java connector Dockerfiles. Their registry latest specs were linted as their own row.

Language Support Connectors Fail (enforced) before fixes Fail after fixes Warn (advisory)
manifest-only certified 42 1 0 0
manifest-only community 460 0 0 0
manifest-only archived/unknown 14 0 0 0
python certified 11 0 0 1
python community 77 14 0 2
python archived 6 1 1 0
java (Dockerfile Tests workflow only) certified 4 2 1 1

Remaining enforced failures, both additionalProperties is true:

  • destination-timeplus. It is archived and disabled in both registries, so nobody ships it. Connector CI does not filter by support level, so its tests fail only when a pull request edits it. Not fixed.
  • source-mssql. Its bulk-CDK spec renders the @JsonAnyGetter map as a config property named additionalProperties, whose own additionalProperties is a schema. The fix is a Kotlin change to a certified database source that could not be built or verified here, so it is left as a known failure of the Dockerfile Tests workflow's source-mssql leg. That workflow runs only when a pull request changes docker-images/Dockerfile.java-connector*.

destination-snowflake's duplicate order (private_key_password shared order 0 with private_key) is fixed in airbytehq/airbyte#88929. source-postgres and destination-postgres pass every lint. source-mysql (Java) also repeats an order, but no CDK test path runs on it, so it blocks nothing.

source-tplcentral fails additionalProperties is true until airbytehq/airbyte#88928 merges, but its legacy tests: config fails test collection, so the lint does not run on it either way.

Every other lint (oneOf, enum, array items, property types, display_type, nested group, refs, valid JSON Schema, OAuth config paths) has 0 violators.

Tests

  • New unit_tests/test/test_spec_structural_lints.py covers the passing and the failing side of every lint. It also covers find_spec_problems crash handling, format_spec_problems, the advisory warning, and a check that no failure message prints a spec value.
  • test_spec_lint.py gains Docker-path and in-process tests for an enforced structural failure and for an advisory warning.
  • Each lint that reads a keyword value has a test for a non-string value (items.type list, list or object display_type, list or object $schema): the problem is reported at its pointer, and the lint does not crash.
  • poetry run pytest unit_tests/test/: 847 passed, 9 skipped. ruff and mypy are clean.

Migration

The migration note is in cdk-migrations.md, under "Upgrading to the next minor release (unreleased)". It has a table of the enforced lints with the fix for each, the advisory lints, and the checks that were not ported. This is a minor release: only the test harness changes.

🤖 Generated with Claude Code

…onfig paths

Port CAT's structural spec checks into the spec tests (`test_spec` and
`test_docker_image_build_and_spec`), next to the `airbyte_secret` marking
lint. Every lint reports each problem with its JSON pointer and never prints
a value.

Enforced: valid JSON Schema, refs resolved, additionalProperties is true,
oneOf usage, enum usage, array items, property types, display_type,
duplicate order, nested group and OAuth config paths.

Advisory, reported as the new `SpecLintWarning`: forbidden keywords and
object not empty. Their remaining violators build their specs in code and
cannot be fixed without the spec backward-compatibility check calling the
fix breaking.

A lint that crashes on an unexpected spec is reported as a problem of that
lint instead of raising.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… crashing

Three enforced spec lints crashed on keyword values that are not hashable
strings: `array items` on an `items.type` list such as `[string, null]`,
`display_type` on a list or object value, and `valid JSON Schema` on a
non-string `$schema`. `find_spec_problems` turned the crash into "The lint
could not run: TypeError." with no pointer, so the author could not find the
problem. Each lint now reports the pointer like any other problem, and the
schema check reuses `_catalog_checks._meta_validator`, which falls back to
draft 7 for a non-string `$schema` (its meta-schema then reports it).

The migration note now names the two connectors that still fail an enforced
lint (archived destination-timeplus, and source-mssql in airbyte's Dockerfile
Tests workflow) instead of claiming every failing connector was fixed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s that run the spec tests

The note said the monorepo connectors that failed an enforced lint were
fixed "with two known exceptions". source-tplcentral also fails until its
separate, non-blocking spec fix merges, because its legacy `tests:` config
keeps the lints from running at all. Scope the sentence to connectors whose
CI runs the CDK spec tests and list source-tplcentral as a third exception.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

👋 Greetings, Airbyte Team Member!

Here are some helpful tips and reminders for your convenience.

💡 Show Tips and Tricks

Testing This CDK Version

You 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-structural-lints#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-structural-lints

PR Slash Commands

Airbyte Maintainers can execute the following slash commands on your PR:

  • /autofix - Fixes most formatting and linting issues
  • /poetry-lock - Updates poetry.lock file
  • /test - Runs connector tests with the updated CDK
  • /prerelease - Triggers a prerelease publish with default arguments
  • /poe build - Regenerate git-committed build artifacts, such as the pydantic models which are generated from the manifest JSON schema in YAML.
  • /poe <command> - Runs any poe command in the CDK environment
📚 Show Repo Guidance

Helpful Resources

📝 Edit this welcome message.

Comment on lines +508 to +509
"`/advanced_auth/predicate_key` names the config path `/credentials/method`, which is "
"not a property of `connectionSpecification`.",
Comment on lines +510 to +512
"`/advanced_auth/oauth_config_specification/complete_oauth_output_specification/"
"properties/refresh_token/path_in_connector_config` names the config path "
"`/refresh_token`, which is not a property of `connectionSpecification`.",
@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

PyTest Results (Fast)

5 817 tests  +69   5 786 ✅ +68   11m 48s ⏱️ -22s
    1 suites ± 0      31 💤 + 1 
    1 files   ± 0       0 ❌ ± 0 

Results for commit 01f7ebb. ± Comparison against base commit 2082c2d.

This pull request skips 1 test.
unit_tests.resources.source_pokeapi_w_components_py.integration_tests.test_airbyte_standards.TestSuiteSourcePokeAPI ‑ test_docker_image_spec_backward_compatibility

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

PyTest Results (Full)

5 820 tests  +69   5 789 ✅ +68   16m 36s ⏱️ -24s
    1 suites ± 0      31 💤 + 1 
    1 files   ± 0       0 ❌ ± 0 

Results for commit 01f7ebb. ± Comparison against base commit 2082c2d.

This pull request skips 1 test.
unit_tests.resources.source_pokeapi_w_components_py.integration_tests.test_airbyte_standards.TestSuiteSourcePokeAPI ‑ test_docker_image_spec_backward_compatibility

♻️ This comment has been updated with latest results.

Dropping the null branch of anyOf: [X, null] rejects saved configs that set
the field to null. Rewrite it as X with type: [<X's type>, "null"] instead,
which accepts exactly the same configs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant