Skip to content

feat: world-class SDK — typed domains, four real roles, compat declaration, conformance harnesses#10

Merged
fas89 merged 4 commits into
mainfrom
feat/world-class-sdk-types-harness
Jun 26, 2026
Merged

feat: world-class SDK — typed domains, four real roles, compat declaration, conformance harnesses#10
fas89 merged 4 commits into
mainfrom
feat/world-class-sdk-types-harness

Conversation

@fas89

@fas89 fas89 commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

What & why

Part 1 of the "world-class SDK + plugin" push. Closes the SDK-side gaps from a
multi-agent audit: typed value domains (with a real fail-safe fix), the four-role
promise made true, a plugin↔CLI compatibility declaration, the three
previously-fictional role harnesses, and zero-dep snapshot testing.

Raises api-design and conformance toward 10/10; lands the SDK half of the
version-negotiation fix (CLI half is a follow-up PR).

Changes (each its own green commit)

  1. feat(domains)Severity/ActionStatus/Phase as zero-dep str-enums
    (avoids enum.StrEnum 3.11+ and backports.strenum to keep stdlib-only).
    Fixes the severity footgun: Validator.apply bucketed by raw string and
    tallied failures from literal keys, so a typo'd severity ("errror") silently
    escaped the failure tally. Severity.coerce now fails safe (unknown → ERROR,
    surfaced in warnings). Regression test pins it.
  2. feat(compat)SDK_PROTOCOL_VERSION, MIN/MAX_CLI_VERSION,
    cli_requirement(), and PluginMetadata.{sdk_protocol_version,requires_cli}.
    The CLI's compat check read these off the (renamed-away) fluid_provider_sdk;
    the live SDK now declares them. SDK declares, CLI gates (dbt model).
  3. feat(roles,testing)InfraProvider + CatalogAdapter are now real
    ABCs
    (were doc fiction); apply left abstract so a provider that forgets it
    fails loudly, not silently. PluginCapabilities + capabilities(). Ship the
    3 advertised-but-missing role harnesses; raise the universal floor
    (capabilities/compat declared, plan JSON-serialisable, apply-reflects-plan).
    Zero-dep assert_plan_matches_snapshot (syrupy UX, no dependency).
  4. docs(changelog) — record the above under [Unreleased].

Borrow-before-build

pluggy + stevedore (manager lifecycle), dbt require-dbt-version (declare-and-gate),
dbt-tests-adapter (tiered base harnesses), syrupy (snapshot UX), CPython enum
HOWTO (pre-3.11 StrEnum recipe). Divergences (no backports.strenum, vendored
~30-line snapshot) are deliberate to preserve the zero-dependency invariant.

Verification

  • 136 tests pass (was 51); ruff + black clean at CI-latest (0.15.20 / 26.5.1).
  • Security review: no findings — JSON-only serialization, no eval/exec/pickle/
    yaml/subprocess/network; the one file-writing helper takes static test-literal
    paths (not an untrusted boundary); the real traversal guard in
    CustomScaffold.apply is untouched.
  • Live test (clean-room, no source on PYTHONPATH): built wheel + twine check pass; clean install imports the full new surface; the 3 shipped example
    plugins pass against the wheel (25/29/21); a fresh out-of-tree InfraProvider
    plugin
    passes 23 conformance tests against the installed wheel and is
    discovered + loaded via importlib.metadata (the CLI's mechanism).

Zero-dependency invariant preserved (pip install pulls only stdlib).

fas89 added 4 commits June 26, 2026 19:13
Replace three bare-`str` value domains with `str`-mixed enums (the zero-dep
pre-3.11 StrEnum recipe; we avoid `enum.StrEnum` 3.11+ and `backports.strenum`
to keep the SDK stdlib-only). Members equal and serialise as their value, and
an explicit `__str__` gives StrEnum semantics on 3.10.

Fixes the severity footgun: the reference `Validator.apply` previously bucketed
`counts[severity]` by raw string and tallied `failed` only from the literal
"error"/"critical" keys, so a typo'd severity (e.g. "errror") landed in a
phantom bucket and silently escaped the failure tally. `Severity.coerce` now
normalises aliases and fails safe — an unrecognised severity counts as ERROR
and is surfaced in `ExecutionResult.warnings`. `Finding` normalises severity on
construction. Regression test pins the behaviour.

Borrowed: dbt-core closed-vocabulary + loud-coercion posture; CPython enum
HOWTO StrEnum recipe.
…nd-gate)

The CLI's compat check reads MIN_CLI_VERSION/MAX_CLI_VERSION off the SDK module
and otherwise falls back to the renamed-away `fluid_provider_sdk` (dead import),
so the handshake never fired. Give the live SDK the proper declaration surface:

- version.py: SDK_PROTOCOL_VERSION (interface generation), MIN/MAX_CLI_VERSION,
  and cli_requirement() -> PEP 440 specifier string.
- PluginMetadata: carry sdk_protocol_version + requires_cli (inherits the SDK
  default when unset), surfaced in to_dict() and the default get_plugin_info().
- Export the surface from the package root; pin it in the strict public-API test.

The SDK only declares (stays stdlib-only); the CLI does the SpecifierSet gate.
Borrowed: dbt require-dbt-version declare-and-gate model.
…esses

The README/docs promised four roles and "15+ free tests for all 4 roles", but
only CustomScaffold and Validator existed and InfraProvider/CatalogAdapter were
doc fiction; the advertised ValidatorTestHarness / InfraProviderTestHarness /
CatalogAdapterTestHarness raised ImportError. Close the gap at the root:

- New role ABCs InfraProvider (role="provider") and CatalogAdapter
  (role="catalog"), each with an action builder (provision_action /
  catalog_entry_action) and apply LEFT ABSTRACT so a provider that forgets to
  implement it fails loudly instead of silently no-op'ing a deployment.
- Typed PluginCapabilities + BasePlugin.capabilities() with per-role defaults
  (replaces the prose-only "capability defaults" the docstrings claimed).
- Ship the three missing role harnesses; raise the universal harness floor:
  capabilities declared, compat declared, plan JSON-serialisable, and an opt-in
  apply-reflects-plan check (no silent action drops).
- Zero-dep golden-file snapshot helper (assert_plan_matches_snapshot) mirroring
  syrupy's UX (fail-on-missing, FLUID_SNAPSHOT_UPDATE=1) — dbt-grade plan pinning
  for every role, no dependency.

Borrowed: dbt-tests-adapter tiered base-class pattern; syrupy snapshot UX.
136 tests pass (toy plugins drive the full per-role conformance suite).
@fas89
fas89 merged commit 22b4bdf into main Jun 26, 2026
9 checks passed
@fas89
fas89 deleted the feat/world-class-sdk-types-harness branch June 26, 2026 17:47
fas89 added a commit that referenced this pull request Jun 27, 2026
…ns, conformance, compat) (#16)

Finalizes the CHANGELOG [Unreleased] section as [0.10.0] and bumps both
version constants (pyproject + fluid_sdk.version.SDK_VERSION) 0.9.1 -> 0.10.0.

Ships the world-class SDK series merged since v0.9.1 (no PyPI release had
carried it): #10 typed domains + four real role ABCs (InfraProvider,
CatalogAdapter) + PluginCapabilities + SDK<->CLI compat declaration +
conformance harnesses; #11-#15 type/doc-honesty cascade and docs-honesty
CI gate; #9 actions/checkout bump.

SDK_PROTOCOL_VERSION stays 1 (additive, non-breaking). MIN_CLI_VERSION
unchanged (0.7.0).
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