mikebridge commented on code in PR #45395:
URL: https://github.com/apache/superset/pull/45395#discussion_r4331530820


##########
superset-core/src/superset_core/semantic_layers/testing.py:
##########
@@ -59,3 +78,250 @@ def semantic_layer_registration(monkeypatch: 
pytest.MonkeyPatch) -> None:
     integration lane. Use in isolated provider test processes, not a live host.
     """
     monkeypatch.setattr(decorators, "semantic_layer", _registration)
+
+
+# Keep test data and provider construction in the provider's own fixtures. This
+# suite consumes the real SDK types rather than a copy of a factory signature.
+class SemanticProviderConformance:
+    """Subclass in a ``Test...`` class and supply the documented provider 
fixtures.
+
+    Query fixtures seed discriminating data and provide independent expected
+    Arrow tables for identity, grain, NULL, grouped OR, and order-before-limit.
+    Error fixtures call real provider methods against controlled backend 
outcomes.
+    See the provider author guide for every fixture and the limits of this 
suite.
+    """
+
+    def test_catalog_identity(
+        self, semantic_view: SemanticView, semantic_reloaded_view: SemanticView
+    ) -> None:
+        """Stable selections survive reloads without ambiguous catalog 
members."""
+        assert semantic_view.uid()
+        assert semantic_view.uid() == semantic_reloaded_view.uid()
+        assert _catalog(semantic_view) == _catalog(semantic_reloaded_view)
+
+    @pytest.mark.parametrize(
+        "case", ["identity", "grain", "null", "not_null", "or", "order_limit"]
+    )
+    def test_query_outputs(
+        self,
+        case: str,
+        semantic_view: SemanticView,
+        semantic_query_cases: Mapping[str, tuple[SemanticQuery, pa.Table]],
+    ) -> None:
+        """Compare executed results, including column names and row order."""
+        query: SemanticQuery
+        expected: pa.Table
+        query, expected = semantic_query_cases[case]
+        assert expected.num_rows > 0, (
+            f"{case} must distinguish a correct result from no data"
+        )
+        assert query.order or expected.num_rows == 1, (
+            f"{case}: multi-row fixtures need explicit order"
+        )
+        assert set(query.dimensions) <= semantic_view.get_dimensions()
+        assert set(query.metrics) <= semantic_view.get_metrics()
+        if case == "grain":
+            assert any(dimension.grain is not None for dimension in 
query.dimensions)
+        elif case in {"null", "not_null"}:
+            operator: Operator = (
+                Operator.IS_NULL if case == "null" else Operator.IS_NOT_NULL
+            )
+            assert any(
+                isinstance(item, Filter) and item.operator == operator
+                for item in query.filters or set()
+            )
+        elif case == "or":
+            assert any(isinstance(item, OrFilter) for item in query.filters or 
set())
+        elif case == "order_limit":
+            assert query.order
+            assert query.limit is not None
+            assert query.limit > 0
+            full: SemanticResult = semantic_view.get_table(
+                replace(query, limit=None, offset=None)
+            )
+            assert full.results.num_rows > query.limit, "seed more rows than 
the limit"
+            assert full.results.slice(query.offset or 0, 
query.limit).equals(expected)
+        result: SemanticResult = semantic_view.get_table(query)
+        assert result.results.equals(expected), (

Review Comment:
   Fixed in 0cb01305d7d8ebca0e231953036ba6ef3d239d7b. The kit compares typed 
columns by name, so field order and nullability no longer reject equivalent 
results. This applies to the query, order-before-limit and capability checks. 
The regressions cover reordered and nonnullable columns; wrong types, values, 
row order and duplicate names still fail, with schema details in the diagnostic.
   
   One follow-up hardening item: the negative comparison regressions should 
match the specific assertion diagnostic, so a future fixture change cannot make 
an unrelated assertion satisfy the test.



##########
superset-core/src/superset_core/semantic_layers/testing.py:
##########
@@ -59,3 +78,250 @@ def semantic_layer_registration(monkeypatch: 
pytest.MonkeyPatch) -> None:
     integration lane. Use in isolated provider test processes, not a live host.
     """
     monkeypatch.setattr(decorators, "semantic_layer", _registration)
+
+
+# Keep test data and provider construction in the provider's own fixtures. This
+# suite consumes the real SDK types rather than a copy of a factory signature.
+class SemanticProviderConformance:
+    """Subclass in a ``Test...`` class and supply the documented provider 
fixtures.
+
+    Query fixtures seed discriminating data and provide independent expected
+    Arrow tables for identity, grain, NULL, grouped OR, and order-before-limit.
+    Error fixtures call real provider methods against controlled backend 
outcomes.
+    See the provider author guide for every fixture and the limits of this 
suite.
+    """
+
+    def test_catalog_identity(
+        self, semantic_view: SemanticView, semantic_reloaded_view: SemanticView
+    ) -> None:
+        """Stable selections survive reloads without ambiguous catalog 
members."""
+        assert semantic_view.uid()
+        assert semantic_view.uid() == semantic_reloaded_view.uid()
+        assert _catalog(semantic_view) == _catalog(semantic_reloaded_view)
+
+    @pytest.mark.parametrize(
+        "case", ["identity", "grain", "null", "not_null", "or", "order_limit"]
+    )
+    def test_query_outputs(
+        self,
+        case: str,
+        semantic_view: SemanticView,
+        semantic_query_cases: Mapping[str, tuple[SemanticQuery, pa.Table]],
+    ) -> None:
+        """Compare executed results, including column names and row order."""
+        query: SemanticQuery
+        expected: pa.Table
+        query, expected = semantic_query_cases[case]
+        assert expected.num_rows > 0, (
+            f"{case} must distinguish a correct result from no data"
+        )
+        assert query.order or expected.num_rows == 1, (
+            f"{case}: multi-row fixtures need explicit order"
+        )
+        assert set(query.dimensions) <= semantic_view.get_dimensions()
+        assert set(query.metrics) <= semantic_view.get_metrics()
+        if case == "grain":
+            assert any(dimension.grain is not None for dimension in 
query.dimensions)
+        elif case in {"null", "not_null"}:
+            operator: Operator = (
+                Operator.IS_NULL if case == "null" else Operator.IS_NOT_NULL
+            )
+            assert any(
+                isinstance(item, Filter) and item.operator == operator
+                for item in query.filters or set()
+            )
+        elif case == "or":
+            assert any(isinstance(item, OrFilter) for item in query.filters or 
set())
+        elif case == "order_limit":
+            assert query.order
+            assert query.limit is not None
+            assert query.limit > 0
+            full: SemanticResult = semantic_view.get_table(
+                replace(query, limit=None, offset=None)
+            )
+            assert full.results.num_rows > query.limit, "seed more rows than 
the limit"
+            assert full.results.slice(query.offset or 0, 
query.limit).equals(expected)
+        result: SemanticResult = semantic_view.get_table(query)
+        assert result.results.equals(expected), (
+            f"{case}: expected {expected.to_pydict()}, got 
{result.results.to_pydict()}"
+        )
+        assert set(result.results.column_names) == {
+            member.name for member in [*query.dimensions, *query.metrics]
+        }
+
+    @pytest.mark.parametrize("method", ["get_table", "get_values", 
"get_row_count"])
+    @pytest.mark.parametrize("kind", ["refusal", "fault", "incomplete", 
"unverified"])
+    def test_error_categories(
+        self,
+        method: str,
+        kind: str,
+        semantic_error_cases: Mapping[str, Callable[[], SemanticResult]],
+    ) -> None:
+        """Refusals and completeness are typed; operational faults stay 
faults."""
+        operation: Callable[[], SemanticResult] = semantic_error_cases[
+            f"{method}:{kind}"
+        ]
+        error: pytest.ExceptionInfo[Exception]
+        # Operational fault classes are provider-owned; classify the caught 
error below.
+        with pytest.raises(Exception) as error:  # noqa: PT011
+            operation()
+        if kind == "refusal":
+            assert isinstance(error.value, SemanticQueryRejectedError)
+            assert isinstance(error.value.code, SemanticQueryErrorCode)
+        elif kind in {"incomplete", "unverified"}:
+            assert isinstance(error.value, SemanticResultCompletenessError)
+            assert error.value.reason == kind
+        else:
+            assert not isinstance(

Review Comment:
   Fixed in 0cb01305d7d8ebca0e231953036ba6ef3d239d7b. The fault lane rejects 
TypeError, AttributeError, NameError, AssertionError and KeyError, in addition 
to refusal/completeness exceptions. Each programming-error variant has a 
regression. Backend-specific exception classes remain provider-owned, and the 
guide calls for provider lanes to assert their exact backend fault type too.
   
   This implements the minimum guard you suggested; it is still a heuristic, 
since other programming errors can pass. A fixture-declared expected fault type 
is the stronger follow-up, alongside the deferred value/count success cases.



##########
superset-core/src/superset_core/semantic_layers/testing.py:
##########
@@ -59,3 +78,250 @@ def semantic_layer_registration(monkeypatch: 
pytest.MonkeyPatch) -> None:
     integration lane. Use in isolated provider test processes, not a live host.
     """
     monkeypatch.setattr(decorators, "semantic_layer", _registration)
+
+
+# Keep test data and provider construction in the provider's own fixtures. This
+# suite consumes the real SDK types rather than a copy of a factory signature.
+class SemanticProviderConformance:
+    """Subclass in a ``Test...`` class and supply the documented provider 
fixtures.
+
+    Query fixtures seed discriminating data and provide independent expected
+    Arrow tables for identity, grain, NULL, grouped OR, and order-before-limit.
+    Error fixtures call real provider methods against controlled backend 
outcomes.
+    See the provider author guide for every fixture and the limits of this 
suite.
+    """
+
+    def test_catalog_identity(
+        self, semantic_view: SemanticView, semantic_reloaded_view: SemanticView
+    ) -> None:
+        """Stable selections survive reloads without ambiguous catalog 
members."""
+        assert semantic_view.uid()
+        assert semantic_view.uid() == semantic_reloaded_view.uid()
+        assert _catalog(semantic_view) == _catalog(semantic_reloaded_view)
+
+    @pytest.mark.parametrize(
+        "case", ["identity", "grain", "null", "not_null", "or", "order_limit"]
+    )
+    def test_query_outputs(
+        self,
+        case: str,
+        semantic_view: SemanticView,
+        semantic_query_cases: Mapping[str, tuple[SemanticQuery, pa.Table]],
+    ) -> None:
+        """Compare executed results, including column names and row order."""
+        query: SemanticQuery
+        expected: pa.Table
+        query, expected = semantic_query_cases[case]
+        assert expected.num_rows > 0, (
+            f"{case} must distinguish a correct result from no data"
+        )
+        assert query.order or expected.num_rows == 1, (
+            f"{case}: multi-row fixtures need explicit order"
+        )
+        assert set(query.dimensions) <= semantic_view.get_dimensions()
+        assert set(query.metrics) <= semantic_view.get_metrics()
+        if case == "grain":
+            assert any(dimension.grain is not None for dimension in 
query.dimensions)
+        elif case in {"null", "not_null"}:
+            operator: Operator = (
+                Operator.IS_NULL if case == "null" else Operator.IS_NOT_NULL
+            )
+            assert any(
+                isinstance(item, Filter) and item.operator == operator
+                for item in query.filters or set()
+            )
+        elif case == "or":
+            assert any(isinstance(item, OrFilter) for item in query.filters or 
set())
+        elif case == "order_limit":
+            assert query.order
+            assert query.limit is not None
+            assert query.limit > 0
+            full: SemanticResult = semantic_view.get_table(
+                replace(query, limit=None, offset=None)
+            )
+            assert full.results.num_rows > query.limit, "seed more rows than 
the limit"
+            assert full.results.slice(query.offset or 0, 
query.limit).equals(expected)
+        result: SemanticResult = semantic_view.get_table(query)
+        assert result.results.equals(expected), (
+            f"{case}: expected {expected.to_pydict()}, got 
{result.results.to_pydict()}"
+        )
+        assert set(result.results.column_names) == {
+            member.name for member in [*query.dimensions, *query.metrics]
+        }
+
+    @pytest.mark.parametrize("method", ["get_table", "get_values", 
"get_row_count"])
+    @pytest.mark.parametrize("kind", ["refusal", "fault", "incomplete", 
"unverified"])
+    def test_error_categories(
+        self,
+        method: str,
+        kind: str,
+        semantic_error_cases: Mapping[str, Callable[[], SemanticResult]],
+    ) -> None:
+        """Refusals and completeness are typed; operational faults stay 
faults."""
+        operation: Callable[[], SemanticResult] = semantic_error_cases[
+            f"{method}:{kind}"
+        ]
+        error: pytest.ExceptionInfo[Exception]
+        # Operational fault classes are provider-owned; classify the caught 
error below.
+        with pytest.raises(Exception) as error:  # noqa: PT011
+            operation()
+        if kind == "refusal":
+            assert isinstance(error.value, SemanticQueryRejectedError)
+            assert isinstance(error.value.code, SemanticQueryErrorCode)
+        elif kind in {"incomplete", "unverified"}:
+            assert isinstance(error.value, SemanticResultCompletenessError)
+            assert error.value.reason == kind
+        else:
+            assert not isinstance(
+                error.value,
+                (SemanticQueryRejectedError, SemanticResultCompletenessError),
+            )
+
+    def test_advertised_capabilities(
+        self,
+        semantic_view: SemanticView,
+        semantic_capability_cases: Mapping[str, tuple[SemanticQuery, 
pa.Table]],
+    ) -> None:
+        """Every advertised feature has a result-level witness in this lane."""
+        feature: SemanticViewFeature | str
+        for feature in semantic_view.features:
+            name: str = (
+                feature.value if isinstance(feature, SemanticViewFeature) else 
feature
+            )
+            query: SemanticQuery
+            expected: pa.Table
+            query, expected = semantic_capability_cases[name]
+            assert expected.num_rows > 0
+            assert query.order or expected.num_rows == 1, (
+                f"{name}: multi-row fixtures need explicit order"
+            )
+            if name == "ROW_OFFSET":
+                assert query.offset is not None
+                assert query.offset > 0
+                assert query.limit is not None
+                assert query.limit > 0
+            elif name in {"GROUP_LIMIT", "GROUP_OTHERS"}:

Review Comment:
   Fixed in 0cb01305d7d8ebca0e231953036ba6ef3d239d7b. Each SDK capability is a 
separate parametrized case, with an explicit skip when unadvertised. The two 
ad-hoc cases require an AdhocExpression in the selected dimensions or order 
keys respectively; plain-query witnesses fail before provider execution.



##########
superset-core/src/superset_core/semantic_layers/testing.py:
##########
@@ -59,3 +78,250 @@ def semantic_layer_registration(monkeypatch: 
pytest.MonkeyPatch) -> None:
     integration lane. Use in isolated provider test processes, not a live host.
     """
     monkeypatch.setattr(decorators, "semantic_layer", _registration)
+
+
+# Keep test data and provider construction in the provider's own fixtures. This
+# suite consumes the real SDK types rather than a copy of a factory signature.
+class SemanticProviderConformance:
+    """Subclass in a ``Test...`` class and supply the documented provider 
fixtures.
+
+    Query fixtures seed discriminating data and provide independent expected
+    Arrow tables for identity, grain, NULL, grouped OR, and order-before-limit.
+    Error fixtures call real provider methods against controlled backend 
outcomes.
+    See the provider author guide for every fixture and the limits of this 
suite.
+    """
+
+    def test_catalog_identity(
+        self, semantic_view: SemanticView, semantic_reloaded_view: SemanticView
+    ) -> None:
+        """Stable selections survive reloads without ambiguous catalog 
members."""
+        assert semantic_view.uid()
+        assert semantic_view.uid() == semantic_reloaded_view.uid()
+        assert _catalog(semantic_view) == _catalog(semantic_reloaded_view)
+
+    @pytest.mark.parametrize(
+        "case", ["identity", "grain", "null", "not_null", "or", "order_limit"]
+    )
+    def test_query_outputs(
+        self,
+        case: str,
+        semantic_view: SemanticView,
+        semantic_query_cases: Mapping[str, tuple[SemanticQuery, pa.Table]],
+    ) -> None:
+        """Compare executed results, including column names and row order."""
+        query: SemanticQuery
+        expected: pa.Table
+        query, expected = semantic_query_cases[case]
+        assert expected.num_rows > 0, (
+            f"{case} must distinguish a correct result from no data"
+        )
+        assert query.order or expected.num_rows == 1, (
+            f"{case}: multi-row fixtures need explicit order"
+        )
+        assert set(query.dimensions) <= semantic_view.get_dimensions()
+        assert set(query.metrics) <= semantic_view.get_metrics()
+        if case == "grain":
+            assert any(dimension.grain is not None for dimension in 
query.dimensions)
+        elif case in {"null", "not_null"}:
+            operator: Operator = (
+                Operator.IS_NULL if case == "null" else Operator.IS_NOT_NULL
+            )
+            assert any(
+                isinstance(item, Filter) and item.operator == operator
+                for item in query.filters or set()
+            )
+        elif case == "or":
+            assert any(isinstance(item, OrFilter) for item in query.filters or 
set())
+        elif case == "order_limit":
+            assert query.order
+            assert query.limit is not None
+            assert query.limit > 0
+            full: SemanticResult = semantic_view.get_table(
+                replace(query, limit=None, offset=None)
+            )
+            assert full.results.num_rows > query.limit, "seed more rows than 
the limit"
+            assert full.results.slice(query.offset or 0, 
query.limit).equals(expected)
+        result: SemanticResult = semantic_view.get_table(query)
+        assert result.results.equals(expected), (
+            f"{case}: expected {expected.to_pydict()}, got 
{result.results.to_pydict()}"
+        )
+        assert set(result.results.column_names) == {
+            member.name for member in [*query.dimensions, *query.metrics]
+        }
+
+    @pytest.mark.parametrize("method", ["get_table", "get_values", 
"get_row_count"])
+    @pytest.mark.parametrize("kind", ["refusal", "fault", "incomplete", 
"unverified"])
+    def test_error_categories(
+        self,
+        method: str,
+        kind: str,
+        semantic_error_cases: Mapping[str, Callable[[], SemanticResult]],
+    ) -> None:
+        """Refusals and completeness are typed; operational faults stay 
faults."""
+        operation: Callable[[], SemanticResult] = semantic_error_cases[
+            f"{method}:{kind}"
+        ]
+        error: pytest.ExceptionInfo[Exception]
+        # Operational fault classes are provider-owned; classify the caught 
error below.
+        with pytest.raises(Exception) as error:  # noqa: PT011
+            operation()
+        if kind == "refusal":
+            assert isinstance(error.value, SemanticQueryRejectedError)
+            assert isinstance(error.value.code, SemanticQueryErrorCode)
+        elif kind in {"incomplete", "unverified"}:
+            assert isinstance(error.value, SemanticResultCompletenessError)
+            assert error.value.reason == kind
+        else:
+            assert not isinstance(
+                error.value,
+                (SemanticQueryRejectedError, SemanticResultCompletenessError),
+            )
+
+    def test_advertised_capabilities(
+        self,
+        semantic_view: SemanticView,
+        semantic_capability_cases: Mapping[str, tuple[SemanticQuery, 
pa.Table]],
+    ) -> None:
+        """Every advertised feature has a result-level witness in this lane."""
+        feature: SemanticViewFeature | str
+        for feature in semantic_view.features:
+            name: str = (
+                feature.value if isinstance(feature, SemanticViewFeature) else 
feature
+            )
+            query: SemanticQuery
+            expected: pa.Table
+            query, expected = semantic_capability_cases[name]
+            assert expected.num_rows > 0
+            assert query.order or expected.num_rows == 1, (
+                f"{name}: multi-row fixtures need explicit order"
+            )
+            if name == "ROW_OFFSET":
+                assert query.offset is not None
+                assert query.offset > 0
+                assert query.limit is not None
+                assert query.limit > 0
+            elif name in {"GROUP_LIMIT", "GROUP_OTHERS"}:
+                assert query.group_limit is not None
+                if name == "GROUP_OTHERS":
+                    assert query.group_limit.group_others
+            assert semantic_view.get_table(query).results.equals(expected), 
name
+
+    def test_metadata_generation_and_unbound_bypass(
+        self,
+        semantic_make_view: Callable[[str | None], SemanticView],
+        semantic_change_catalog: Callable[[], None],
+    ) -> None:
+        """Changed generations and unbound calls cannot reuse a stale catalog.
+
+        The change fixture must add a member on each call. Factories and 
transport
+        stay provider-owned; uncached legacy providers may report a None token.
+        """
+        first: SemanticView = semantic_make_view("generation-a")
+        before: tuple[set[str], set[tuple[str, str | None]]] = _catalog(first)
+        assert first.metadata_cache_token in {None, "generation-a"}
+        semantic_change_catalog()
+        second: SemanticView = semantic_make_view("generation-b")
+        after: tuple[set[str], set[tuple[str, str | None]]] = _catalog(second)
+        assert after != before
+        assert second.metadata_cache_token in {None, "generation-b"}
+        # A captured view must never claim a later discovery token.
+        assert first.metadata_cache_token in {None, "generation-a"}
+        semantic_change_catalog()
+        unbound: SemanticView = semantic_make_view(None)

Review Comment:
   Agreed; fixed in 0cb01305d7d8ebca0e231953036ba6ef3d239d7b by dropping the 
unsupported None-construction leg and its cache-guide sentence. The kit passes 
string tokens matching from_configuration and the host binding path. It still 
verifies catalog freshness across two generations and that an older captured 
view is never relabeled. This does not change the SDK factory signature or 
legacy metadata_cache_token=None echoes. The shell adapter should use that 
required string-token path rather than widen the SDK contract.



##########
superset-core/src/superset_core/semantic_layers/testing.py:
##########
@@ -59,3 +78,250 @@ def semantic_layer_registration(monkeypatch: 
pytest.MonkeyPatch) -> None:
     integration lane. Use in isolated provider test processes, not a live host.
     """
     monkeypatch.setattr(decorators, "semantic_layer", _registration)
+
+
+# Keep test data and provider construction in the provider's own fixtures. This
+# suite consumes the real SDK types rather than a copy of a factory signature.
+class SemanticProviderConformance:
+    """Subclass in a ``Test...`` class and supply the documented provider 
fixtures.
+
+    Query fixtures seed discriminating data and provide independent expected
+    Arrow tables for identity, grain, NULL, grouped OR, and order-before-limit.
+    Error fixtures call real provider methods against controlled backend 
outcomes.
+    See the provider author guide for every fixture and the limits of this 
suite.
+    """
+
+    def test_catalog_identity(
+        self, semantic_view: SemanticView, semantic_reloaded_view: SemanticView
+    ) -> None:
+        """Stable selections survive reloads without ambiguous catalog 
members."""
+        assert semantic_view.uid()
+        assert semantic_view.uid() == semantic_reloaded_view.uid()
+        assert _catalog(semantic_view) == _catalog(semantic_reloaded_view)
+
+    @pytest.mark.parametrize(
+        "case", ["identity", "grain", "null", "not_null", "or", "order_limit"]
+    )
+    def test_query_outputs(
+        self,
+        case: str,
+        semantic_view: SemanticView,
+        semantic_query_cases: Mapping[str, tuple[SemanticQuery, pa.Table]],
+    ) -> None:
+        """Compare executed results, including column names and row order."""
+        query: SemanticQuery
+        expected: pa.Table
+        query, expected = semantic_query_cases[case]
+        assert expected.num_rows > 0, (
+            f"{case} must distinguish a correct result from no data"
+        )
+        assert query.order or expected.num_rows == 1, (
+            f"{case}: multi-row fixtures need explicit order"
+        )
+        assert set(query.dimensions) <= semantic_view.get_dimensions()
+        assert set(query.metrics) <= semantic_view.get_metrics()
+        if case == "grain":
+            assert any(dimension.grain is not None for dimension in 
query.dimensions)
+        elif case in {"null", "not_null"}:
+            operator: Operator = (
+                Operator.IS_NULL if case == "null" else Operator.IS_NOT_NULL
+            )
+            assert any(
+                isinstance(item, Filter) and item.operator == operator
+                for item in query.filters or set()
+            )
+        elif case == "or":
+            assert any(isinstance(item, OrFilter) for item in query.filters or 
set())
+        elif case == "order_limit":
+            assert query.order
+            assert query.limit is not None
+            assert query.limit > 0
+            full: SemanticResult = semantic_view.get_table(
+                replace(query, limit=None, offset=None)
+            )
+            assert full.results.num_rows > query.limit, "seed more rows than 
the limit"
+            assert full.results.slice(query.offset or 0, 
query.limit).equals(expected)
+        result: SemanticResult = semantic_view.get_table(query)
+        assert result.results.equals(expected), (
+            f"{case}: expected {expected.to_pydict()}, got 
{result.results.to_pydict()}"
+        )
+        assert set(result.results.column_names) == {
+            member.name for member in [*query.dimensions, *query.metrics]
+        }
+
+    @pytest.mark.parametrize("method", ["get_table", "get_values", 
"get_row_count"])
+    @pytest.mark.parametrize("kind", ["refusal", "fault", "incomplete", 
"unverified"])
+    def test_error_categories(
+        self,
+        method: str,
+        kind: str,
+        semantic_error_cases: Mapping[str, Callable[[], SemanticResult]],
+    ) -> None:
+        """Refusals and completeness are typed; operational faults stay 
faults."""
+        operation: Callable[[], SemanticResult] = semantic_error_cases[
+            f"{method}:{kind}"
+        ]
+        error: pytest.ExceptionInfo[Exception]
+        # Operational fault classes are provider-owned; classify the caught 
error below.
+        with pytest.raises(Exception) as error:  # noqa: PT011
+            operation()
+        if kind == "refusal":
+            assert isinstance(error.value, SemanticQueryRejectedError)
+            assert isinstance(error.value.code, SemanticQueryErrorCode)
+        elif kind in {"incomplete", "unverified"}:
+            assert isinstance(error.value, SemanticResultCompletenessError)
+            assert error.value.reason == kind
+        else:
+            assert not isinstance(
+                error.value,
+                (SemanticQueryRejectedError, SemanticResultCompletenessError),
+            )
+
+    def test_advertised_capabilities(
+        self,
+        semantic_view: SemanticView,
+        semantic_capability_cases: Mapping[str, tuple[SemanticQuery, 
pa.Table]],
+    ) -> None:
+        """Every advertised feature has a result-level witness in this lane."""
+        feature: SemanticViewFeature | str
+        for feature in semantic_view.features:
+            name: str = (
+                feature.value if isinstance(feature, SemanticViewFeature) else 
feature
+            )
+            query: SemanticQuery
+            expected: pa.Table
+            query, expected = semantic_capability_cases[name]
+            assert expected.num_rows > 0
+            assert query.order or expected.num_rows == 1, (
+                f"{name}: multi-row fixtures need explicit order"
+            )
+            if name == "ROW_OFFSET":
+                assert query.offset is not None
+                assert query.offset > 0
+                assert query.limit is not None
+                assert query.limit > 0
+            elif name in {"GROUP_LIMIT", "GROUP_OTHERS"}:
+                assert query.group_limit is not None
+                if name == "GROUP_OTHERS":
+                    assert query.group_limit.group_others
+            assert semantic_view.get_table(query).results.equals(expected), 
name
+
+    def test_metadata_generation_and_unbound_bypass(
+        self,
+        semantic_make_view: Callable[[str | None], SemanticView],
+        semantic_change_catalog: Callable[[], None],
+    ) -> None:
+        """Changed generations and unbound calls cannot reuse a stale catalog.
+
+        The change fixture must add a member on each call. Factories and 
transport
+        stay provider-owned; uncached legacy providers may report a None token.
+        """
+        first: SemanticView = semantic_make_view("generation-a")
+        before: tuple[set[str], set[tuple[str, str | None]]] = _catalog(first)
+        assert first.metadata_cache_token in {None, "generation-a"}
+        semantic_change_catalog()
+        second: SemanticView = semantic_make_view("generation-b")
+        after: tuple[set[str], set[tuple[str, str | None]]] = _catalog(second)
+        assert after != before
+        assert second.metadata_cache_token in {None, "generation-b"}
+        # A captured view must never claim a later discovery token.
+        assert first.metadata_cache_token in {None, "generation-a"}
+        semantic_change_catalog()
+        unbound: SemanticView = semantic_make_view(None)
+        assert _catalog(unbound) != after
+        assert unbound.metadata_cache_token is None
+
+    def test_imports_and_static_schema(
+        self, semantic_provider_class: tuple[str, str]
+    ) -> None:
+        """Import in a fresh interpreter; reject host imports and schema 
I/O."""
+        import os
+        import subprocess
+        import sys
+
+        # Fixed interpreter/code; provider metadata is argv, never shell text.
+        completed: subprocess.CompletedProcess[str] = subprocess.run(  # noqa: 
S603
+            [
+                sys.executable,
+                "-c",
+                "from superset_core.semantic_layers.testing import 
_check_isolation; "
+                "import sys; _check_isolation(*sys.argv[1:])",
+                *semantic_provider_class,
+            ],
+            env={**os.environ, "PYTHONPATH": os.pathsep.join(sys.path)},
+            capture_output=True,
+            text=True,
+            timeout=30,
+            check=False,
+        )
+        assert completed.returncode == 0, completed.stdout + completed.stderr
+
+    # TODO: add the default chronological-order case after its SDK rule lands.

Review Comment:
   Fixed in 0cb01305d7d8ebca0e231953036ba6ef3d239d7b. Removed the TODO and 
updated the guide: the host supplies the default chronological order, while the 
provider must honor the explicit order it receives before limit/offset. The 
existing grain and order-limit witnesses cover that obligation.
   
   I also addressed the documentation notes from your review body: the 
plugin-loading example uses -p before collection, the grain wording matches the 
enforced witness, and the isolation diagnostic identifies transitive framework 
imports. The value/count success gaps are real, including the host's existing 
get_values shape expectation. I suggest a separate follow-up for success 
fixtures and filtered-value assertions; this patch keeps those methods scoped 
to error propagation. The SDK suite passes with 64 tests and four skips for 
unadvertised capabilities.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to