This is an automated email from the ASF dual-hosted git repository. FreeOnePlus pushed a commit to branch feat/v1-02-feature-matrix-version-ranges in repository https://gitbox.apache.org/repos/asf/doris-mcp-server.git
commit c901504b50145e38ac9f011a38a4fd85c532e946 Author: FreeOnePlus <[email protected]> AuthorDate: Fri Jul 31 02:13:42 2026 +0800 feat: add Doris child feature matrix --- doris_mcp_server/tools/doris_feature_matrix.py | 1576 ++++++++++++++++++++++++ test/tools/test_doris_feature_matrix.py | 685 ++++++++++ 2 files changed, 2261 insertions(+) diff --git a/doris_mcp_server/tools/doris_feature_matrix.py b/doris_mcp_server/tools/doris_feature_matrix.py new file mode 100644 index 0000000..77647e8 --- /dev/null +++ b/doris_mcp_server/tools/doris_feature_matrix.py @@ -0,0 +1,1576 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Machine-readable Doris child capability matrix and version range engine.""" + +from __future__ import annotations + +import re +from collections.abc import Iterable, Mapping +from dataclasses import dataclass, field +from datetime import date +from enum import StrEnum +from types import MappingProxyType +from typing import Annotated, Self + +from pydantic import Field, StringConstraints, model_validator + +from .domain_models import ( + CapabilityVariant, + ChildSupportContract, + ContractModel, + Identifier, + NonEmptyText, + ReasonCode, + UnavailableBehavior, +) +from .doris_version import DorisVersion, parse_doris_version_comment + +PROJECT_MINIMUM_DORIS_VERSION = "3.0.0" +PROJECT_SUPPORTED_RANGE = f">={PROJECT_MINIMUM_DORIS_VERSION}" +SOURCE_VERIFIED_ON = date(2026, 7, 31) + +CERTIFICATION_TARGET_VERSIONS = ( + "3.0.3", + "3.1.4", + "4.0.5", + "4.0.6", + "4.0.7", + "4.1.0", + "4.1.1", + "4.1.2", + "4.1.3", +) + +# Certification is evidence-driven. V1-02 defines targets but certifies no patch. +CERTIFIED_DORIS_VERSIONS: tuple[str, ...] = () + +SourceIdentifier = Annotated[ + str, + StringConstraints( + min_length=1, + max_length=128, + pattern=r"^[A-Z][A-Z0-9_]*$", + strip_whitespace=True, + ), +] +PullRequestReference = Annotated[ + str, + StringConstraints(pattern=r"^#[1-9][0-9]*$", strip_whitespace=True), +] + +_VERSION_LITERAL_PATTERN = re.compile( + r"\d+\.\d+\.\d+(?:-(?:alpha|beta|rc)\d*)?", + re.IGNORECASE, +) +_RANGE_CLAUSE_PATTERN = re.compile( + r"(?P<operator>>=|<=|==|>|<)\s*(?P<version>.+)", +) + + +class VersionRangeOperator(StrEnum): + """Supported comparison operators in a Doris version range clause.""" + + GREATER_THAN_OR_EQUAL = ">=" + LESS_THAN_OR_EQUAL = "<=" + EQUAL = "==" + GREATER_THAN = ">" + LESS_THAN = "<" + + +@dataclass(frozen=True, slots=True) +class DorisVersionConstraint: + """One parsed comparison inside a comma-separated version range.""" + + operator: VersionRangeOperator + version: DorisVersion + + def matches(self, candidate: DorisVersion) -> bool: + comparison = candidate.compare(self.version) + if self.operator is VersionRangeOperator.GREATER_THAN_OR_EQUAL: + return comparison >= 0 + if self.operator is VersionRangeOperator.LESS_THAN_OR_EQUAL: + return comparison <= 0 + if self.operator is VersionRangeOperator.EQUAL: + return comparison == 0 + if self.operator is VersionRangeOperator.GREATER_THAN: + return comparison > 0 + return comparison < 0 + + +@dataclass(frozen=True, slots=True) +class DorisVersionRange: + """An AND range such as ``>=4.0.6,<4.1.0``.""" + + expression: str + constraints: tuple[DorisVersionConstraint, ...] = field( + init=False, + repr=False, + ) + + def __post_init__(self) -> None: + clauses = tuple(part.strip() for part in self.expression.split(",")) + if not clauses or any(not clause for clause in clauses): + raise ValueError(f"Invalid Doris version range: {self.expression!r}") + + parsed = tuple(_parse_range_clause(clause) for clause in clauses) + _validate_satisfiable_range(parsed, self.expression) + normalized = ",".join( + f"{constraint.operator.value}{_version_literal(constraint.version)}" + for constraint in parsed + ) + object.__setattr__(self, "expression", normalized) + object.__setattr__(self, "constraints", parsed) + + def contains(self, candidate: DorisVersion) -> bool: + if not candidate.is_parsed: + return False + return all(constraint.matches(candidate) for constraint in self.constraints) + + +class CapabilityVersionScope(StrEnum): + """Doris component set that must satisfy a child version contract.""" + + MASTER_FE = "master_fe" + ALL_FE = "all_fe" + ALL_BE = "all_be" + ALL_COMPONENTS = "all_components" + PROVIDER_WITH_MASTER_FE_BASELINE = "provider_with_master_fe_baseline" + + +@dataclass(frozen=True, slots=True) +class DorisClusterVersionVector: + """Observed versions used for conservative mixed-patch evaluation.""" + + master_fe: DorisVersion + follower_fes: tuple[DorisVersion, ...] = () + backends: tuple[DorisVersion, ...] = () + + @classmethod + def from_comments( + cls, + *, + master_fe: str, + follower_fes: tuple[str, ...] = (), + backends: tuple[str, ...] = (), + ) -> Self: + return cls( + master_fe=parse_doris_version_comment(master_fe), + follower_fes=tuple( + parse_doris_version_comment(comment) for comment in follower_fes + ), + backends=tuple( + parse_doris_version_comment(comment) for comment in backends + ), + ) + + def effective_version( + self, + scope: CapabilityVersionScope, + ) -> DorisVersion | None: + versions: tuple[DorisVersion, ...] + if scope in { + CapabilityVersionScope.MASTER_FE, + CapabilityVersionScope.PROVIDER_WITH_MASTER_FE_BASELINE, + }: + versions = (self.master_fe,) + elif scope is CapabilityVersionScope.ALL_FE: + versions = (self.master_fe, *self.follower_fes) + elif scope is CapabilityVersionScope.ALL_BE: + versions = self.backends + else: + if not self.backends: + return None + versions = (self.master_fe, *self.follower_fes, *self.backends) + return _minimum_parsed_version(versions) + + +class FeatureSourceKind(StrEnum): + """Authority type recorded for a feature source.""" + + RELEASE_NOTE = "release_note" + DOCUMENTATION = "documentation" + PROVIDER_SPECIFICATION = "provider_specification" + + +class FeatureSource(ContractModel): + """Stable authority record referenced by capability variants.""" + + source_id: SourceIdentifier + kind: FeatureSourceKind + title: NonEmptyText + url: NonEmptyText + applicable_ranges: Annotated[ + tuple[NonEmptyText, ...], + Field(min_length=1), + ] + pull_requests: tuple[PullRequestReference, ...] = () + verified_on: date + + @model_validator(mode="after") + def _validate_source(self) -> Self: + if not self.url.startswith("https://"): + raise ValueError("feature source URL must use HTTPS") + _require_unique(self.applicable_ranges, "source applicable ranges") + _require_unique(self.pull_requests, "source pull requests") + for expression in self.applicable_ranges: + DorisVersionRange(expression) + return self + + +class ChildFeatureDefinition(ContractModel): + """Version-facing matrix row for one exact hierarchical child tool.""" + + domain: Identifier + child_name: Identifier + version_scope: CapabilityVersionScope + support_contract: ChildSupportContract + + @property + def feature_id(self) -> str: + return f"{self.domain}.{self.child_name}" + + @model_validator(mode="after") + def _validate_rule_id(self) -> Self: + if self.support_contract.rule_id != self.feature_id: + raise ValueError("support contract rule_id must equal the feature ID") + return self + + +class VersionCertificationStatus(StrEnum): + """Certification relationship for one exact observed Doris patch.""" + + CERTIFIED = "certified" + TARGET_UNCERTIFIED = "target_uncertified" + OUTSIDE_TARGET = "outside_target" + UNKNOWN = "unknown" + + +class FeatureVersionEvaluation(ContractModel): + """Deterministic version-only result, before runtime probes and policy.""" + + feature_id: NonEmptyText + requested_variant: Identifier | None = None + version_scope: CapabilityVersionScope + raw_version: str + observed_fe_version_comments: tuple[str, ...] + observed_be_version_comments: tuple[str, ...] + effective_version: str | None + compatible: bool + reason_code: ReasonCode + matched_variants: tuple[Identifier, ...] + matched_ranges: tuple[NonEmptyText, ...] + source_ids: tuple[SourceIdentifier, ...] + certification_status: VersionCertificationStatus + certification_target: bool + certified: bool + minimum_supported_version: NonEmptyText + + +EXPECTED_DOMAIN_CHILDREN: Mapping[str, tuple[str, ...]] = MappingProxyType( + { + "doris_catalog": ( + "list_catalogs", + "list_databases", + "list_tables", + "get_table_context", + "get_table_size", + ), + "doris_query": ( + "execute_query", + "explain_query", + "get_query_profile", + "diagnose_query_performance", + "list_slow_queries", + "get_adbc_connection_info", + "execute_adbc_query", + ), + "doris_cluster": ( + "get_cluster_overview", + "list_cluster_nodes", + "list_active_tasks", + "get_monitoring_metrics", + "get_memory_stats", + "get_cache_status", + "get_compaction_status", + "get_workload_group_status", + "get_compute_group_status", + "analyze_resource_growth", + "get_runtime_capabilities", + ), + "doris_pipeline": ( + "get_ingestion_status", + "diagnose_ingestion", + "get_materialized_view_status", + "monitor_data_freshness", + "analyze_data_dependencies", + ), + "doris_search": ( + "search_data", + "preview_text_analysis", + "inspect_search_indexes", + "diagnose_search_query", + ), + "doris_governance": ( + "analyze_columns", + "analyze_table_storage", + "get_lineage_capability_status", + "trace_column_lineage", + "analyze_data_access_patterns", + "get_recent_audit_logs", + "list_udfs", + "get_auth_mapping_status", + ), + "doris_lakehouse": ( + "inspect_external_catalog", + "inspect_lakehouse_table", + "inspect_variant_column", + ), + "doris_semantic": ( + "list_semantic_models", + "get_semantic_model_summary", + "get_semantic_context", + "get_semantic_mapping_status", + ), + } +) + + +class DorisFeatureMatrix(ContractModel): + """Complete version policy for the 1.0.0 hierarchical read-only surface.""" + + minimum_supported_version: NonEmptyText + certification_targets: Annotated[ + tuple[NonEmptyText, ...], + Field(min_length=1), + ] + certified_versions: tuple[NonEmptyText, ...] + sources: Annotated[tuple[FeatureSource, ...], Field(min_length=1)] + features: Annotated[tuple[ChildFeatureDefinition, ...], Field(min_length=1)] + + @model_validator(mode="after") + def _validate_matrix(self) -> Self: + minimum = _parse_version_literal(self.minimum_supported_version) + if minimum.prerelease is not None: + raise ValueError("minimum supported version must be stable") + + target_versions = tuple( + _version_literal(_parse_version_literal(value)) + for value in self.certification_targets + ) + certified_versions = tuple( + _version_literal(_parse_version_literal(value)) + for value in self.certified_versions + ) + _require_unique(target_versions, "certification targets") + _require_unique(certified_versions, "certified versions") + if not set(certified_versions).issubset(target_versions): + raise ValueError("certified versions must be certification targets") + + source_ids = tuple(source.source_id for source in self.sources) + _require_unique(source_ids, "feature source IDs") + known_sources = set(source_ids) + + feature_ids = tuple(feature.feature_id for feature in self.features) + rule_ids = tuple(feature.support_contract.rule_id for feature in self.features) + _require_unique(feature_ids, "feature IDs") + _require_unique(rule_ids, "support contract rule IDs") + + actual_domains: dict[str, list[str]] = {} + for feature in self.features: + actual_domains.setdefault(feature.domain, []).append(feature.child_name) + for variant in feature.support_contract.variants: + if not variant.source_references: + raise ValueError( + f"{feature.feature_id}.{variant.name} requires source IDs" + ) + unknown_sources = set(variant.source_references) - known_sources + if unknown_sources: + raise ValueError( + f"{feature.feature_id}.{variant.name} has unknown source IDs: " + f"{sorted(unknown_sources)!r}" + ) + for expression in ( + *variant.supported_ranges, + *variant.excluded_ranges, + ): + DorisVersionRange(expression) + + expected = { + domain: tuple(children) + for domain, children in EXPECTED_DOMAIN_CHILDREN.items() + } + actual = { + domain: tuple(children) for domain, children in actual_domains.items() + } + if actual != expected: + raise ValueError( + "feature matrix must contain the exact ordered 8-domain/47-child " + "contract" + ) + return self + + def get_feature(self, domain: str, child_name: str) -> ChildFeatureDefinition: + feature_id = f"{domain}.{child_name}" + for feature in self.features: + if feature.feature_id == feature_id: + return feature + raise KeyError(feature_id) + + def get_source(self, source_id: str) -> FeatureSource: + for source in self.sources: + if source.source_id == source_id: + return source + raise KeyError(source_id) + + def evaluate( + self, + *, + domain: str, + child_name: str, + versions: DorisClusterVersionVector, + variant_name: str | None = None, + ) -> FeatureVersionEvaluation: + feature = self.get_feature(domain, child_name) + variants = feature.support_contract.variants + if variant_name is not None: + variants = tuple( + variant for variant in variants if variant.name == variant_name + ) + if not variants: + raise KeyError(f"{feature.feature_id}.{variant_name}") + + effective = versions.effective_version(feature.version_scope) + source_ids = _ordered_unique( + source for variant in variants for source in variant.source_references + ) + if effective is None: + return _evaluation( + feature=feature, + variant_name=variant_name, + versions=versions, + effective=None, + compatible=False, + reason_code="DORIS_VERSION_UNKNOWN", + source_ids=source_ids, + matrix=self, + ) + + minimum = _parse_version_literal(self.minimum_supported_version) + if effective.compare(minimum) < 0: + return _evaluation( + feature=feature, + variant_name=variant_name, + versions=versions, + effective=effective, + compatible=False, + reason_code="DORIS_VERSION_BELOW_MINIMUM", + source_ids=source_ids, + matrix=self, + ) + + matched_variants: list[str] = [] + matched_ranges: list[str] = [] + matched_sources: list[str] = [] + for variant in variants: + ranges = matching_version_ranges(variant, effective) + if not ranges: + continue + matched_variants.append(variant.name) + matched_ranges.extend(ranges) + matched_sources.extend(variant.source_references) + + compatible = bool(matched_variants) + return _evaluation( + feature=feature, + variant_name=variant_name, + versions=versions, + effective=effective, + compatible=compatible, + reason_code=( + "VERSION_RANGE_MATCHED" if compatible else "VERSION_RANGE_NOT_MATCHED" + ), + matched_variants=tuple(matched_variants), + matched_ranges=_ordered_unique(matched_ranges), + source_ids=(_ordered_unique(matched_sources) if compatible else source_ids), + matrix=self, + ) + + +def _parse_range_clause(clause: str) -> DorisVersionConstraint: + match = _RANGE_CLAUSE_PATTERN.fullmatch(clause) + if match is None: + raise ValueError(f"Invalid Doris version range clause: {clause!r}") + version = _parse_version_literal(match.group("version").strip()) + return DorisVersionConstraint( + operator=VersionRangeOperator(match.group("operator")), + version=version, + ) + + +def _parse_version_literal(value: str) -> DorisVersion: + if _VERSION_LITERAL_PATTERN.fullmatch(value) is None: + raise ValueError(f"Invalid Doris version literal: {value!r}") + parsed = parse_doris_version_comment(f"Doris version doris-{value}") + if not parsed.is_parsed: + raise ValueError(f"Invalid Doris version literal: {value!r}") + return parsed + + +def _version_literal(version: DorisVersion) -> str: + core = version.core + if core is None: + raise ValueError("Cannot format an unparsed Doris version") + return f"{core}-{version.prerelease}" if version.prerelease else core + + +def _minimum_parsed_version( + versions: tuple[DorisVersion, ...], +) -> DorisVersion | None: + if not versions or any(not version.is_parsed for version in versions): + return None + minimum = versions[0] + for version in versions[1:]: + if version.compare(minimum) < 0: + minimum = version + return minimum + + +def matching_version_ranges( + variant: CapabilityVariant, + version: DorisVersion, +) -> tuple[str, ...]: + """Return supported range expressions after applying exclusions.""" + if not version.is_parsed: + return () + excluded = any( + DorisVersionRange(expression).contains(version) + for expression in variant.excluded_ranges + ) + if excluded: + return () + return tuple( + expression + for expression in variant.supported_ranges + if DorisVersionRange(expression).contains(version) + ) + + +def _validate_satisfiable_range( + constraints: tuple[DorisVersionConstraint, ...], + expression: str, +) -> None: + equal_versions = tuple( + constraint.version + for constraint in constraints + if constraint.operator is VersionRangeOperator.EQUAL + ) + if equal_versions: + reference = equal_versions[0] + if any(version.compare(reference) != 0 for version in equal_versions[1:]): + raise ValueError(f"Contradictory Doris version range: {expression!r}") + if not all(constraint.matches(reference) for constraint in constraints): + raise ValueError(f"Contradictory Doris version range: {expression!r}") + return + + lower: DorisVersionConstraint | None = None + upper: DorisVersionConstraint | None = None + for constraint in constraints: + if constraint.operator in { + VersionRangeOperator.GREATER_THAN, + VersionRangeOperator.GREATER_THAN_OR_EQUAL, + }: + if lower is None or constraint.version.compare(lower.version) > 0: + lower = constraint + elif ( + constraint.version.compare(lower.version) == 0 + and constraint.operator is VersionRangeOperator.GREATER_THAN + ): + lower = constraint + elif constraint.operator in { + VersionRangeOperator.LESS_THAN, + VersionRangeOperator.LESS_THAN_OR_EQUAL, + }: + if upper is None or constraint.version.compare(upper.version) < 0: + upper = constraint + elif ( + constraint.version.compare(upper.version) == 0 + and constraint.operator is VersionRangeOperator.LESS_THAN + ): + upper = constraint + + if lower is None or upper is None: + return + comparison = lower.version.compare(upper.version) + strict_boundary = ( + lower.operator is VersionRangeOperator.GREATER_THAN + or upper.operator is VersionRangeOperator.LESS_THAN + ) + if comparison > 0 or (comparison == 0 and strict_boundary): + raise ValueError(f"Contradictory Doris version range: {expression!r}") + + +def _ordered_unique(values: Iterable[object]) -> tuple[str, ...]: + return tuple(dict.fromkeys(str(value) for value in values)) + + +def _certification_status( + version: DorisVersion | None, + matrix: DorisFeatureMatrix, +) -> VersionCertificationStatus: + if version is None: + return VersionCertificationStatus.UNKNOWN + literal = _version_literal(version) + if literal in matrix.certified_versions: + return VersionCertificationStatus.CERTIFIED + if literal in matrix.certification_targets: + return VersionCertificationStatus.TARGET_UNCERTIFIED + return VersionCertificationStatus.OUTSIDE_TARGET + + +def _evaluation( + *, + feature: ChildFeatureDefinition, + variant_name: str | None, + versions: DorisClusterVersionVector, + effective: DorisVersion | None, + compatible: bool, + reason_code: str, + source_ids: tuple[str, ...], + matrix: DorisFeatureMatrix, + matched_variants: tuple[str, ...] = (), + matched_ranges: tuple[str, ...] = (), +) -> FeatureVersionEvaluation: + certification_status = _certification_status(effective, matrix) + return FeatureVersionEvaluation( + feature_id=feature.feature_id, + requested_variant=variant_name, + version_scope=feature.version_scope, + raw_version=( + effective.raw if effective is not None else versions.master_fe.raw + ), + observed_fe_version_comments=( + versions.master_fe.raw, + *(version.raw for version in versions.follower_fes), + ), + observed_be_version_comments=tuple( + version.raw for version in versions.backends + ), + effective_version=effective.normalized if effective is not None else None, + compatible=compatible, + reason_code=reason_code, + matched_variants=matched_variants, + matched_ranges=matched_ranges, + source_ids=source_ids, + certification_status=certification_status, + certification_target=( + certification_status is VersionCertificationStatus.TARGET_UNCERTIFIED + ), + certified=certification_status is VersionCertificationStatus.CERTIFIED, + minimum_supported_version=matrix.minimum_supported_version, + ) + + +def _require_unique(values: tuple[str, ...], label: str) -> None: + if len(values) != len(set(values)): + raise ValueError(f"{label} must not contain duplicates") + + +def _source( + source_id: str, + kind: FeatureSourceKind, + title: str, + url: str, + *applicable_ranges: str, + pull_requests: tuple[str, ...] = (), +) -> FeatureSource: + return FeatureSource( + source_id=source_id, + kind=kind, + title=title, + url=url, + applicable_ranges=applicable_ranges or (PROJECT_SUPPORTED_RANGE,), + pull_requests=pull_requests, + verified_on=SOURCE_VERIFIED_ON, + ) + + +FEATURE_SOURCES = ( + _source( + "DORIS_DOCS_3X", + FeatureSourceKind.DOCUMENTATION, + "Apache Doris 3.x Documentation", + "https://doris.apache.org/docs/3.x/", + ">=3.0.0,<4.0.0", + ), + _source( + "DORIS_DOCS_4X", + FeatureSourceKind.DOCUMENTATION, + "Apache Doris 4.x Documentation", + "https://doris.apache.org/docs/4.x/", + ">=4.0.0", + ), + _source( + "DORIS_RELEASE_3_0_3", + FeatureSourceKind.RELEASE_NOTE, + "Apache Doris 3.0.3 Release Notes", + "https://doris.apache.org/docs/3.x/releasenotes/v3.0/release-3.0.3/", + "==3.0.3", + ), + _source( + "DORIS_RELEASE_3_1_4", + FeatureSourceKind.RELEASE_NOTE, + "Apache Doris 3.1.4 Release Notes", + "https://doris.apache.org/docs/4.x/releasenotes/v3.1/release-3.1.4/", + "==3.1.4", + ), + _source( + "DORIS_RELEASE_4_0_0", + FeatureSourceKind.RELEASE_NOTE, + "Apache Doris 4.0.0 Release Notes", + "https://doris.apache.org/releases/v4.0/release-4.0.0/", + ">=4.0.0", + ), + _source( + "DORIS_RELEASE_4_0_5", + FeatureSourceKind.RELEASE_NOTE, + "Apache Doris 4.0.5 Release Notes", + "https://doris.apache.org/releases/v4.0/release-4.0.5/", + "==4.0.5", + ), + _source( + "DORIS_RELEASE_4_0_6", + FeatureSourceKind.RELEASE_NOTE, + "Apache Doris 4.0.6 Release Notes", + "https://doris.apache.org/releases/v4.0/release-4.0.6/", + ">=4.0.6,<4.1.0", + pull_requests=("#61004", "#61696"), + ), + _source( + "DORIS_RELEASE_4_0_7", + FeatureSourceKind.RELEASE_NOTE, + "Apache Doris 4.0.7 Release Notes", + "https://doris.apache.org/releases/v4.0/release-4.0.7/", + ">=4.0.7,<4.1.0", + pull_requests=( + "#62141", + "#63206", + "#63576", + "#63654", + "#64381", + "#64569", + "#64742", + ), + ), + _source( + "DORIS_RELEASE_4_1_0", + FeatureSourceKind.RELEASE_NOTE, + "Apache Doris 4.1.0 Release Notes", + "https://doris.apache.org/releases/v4.1/release-4.1.0/", + ">=4.1.0", + ), + _source( + "DORIS_RELEASE_4_1_1", + FeatureSourceKind.RELEASE_NOTE, + "Apache Doris 4.1.1 Release Notes", + "https://doris.apache.org/docs/4.x/releasenotes/v4.1/release-4.1.1/", + ">=4.1.1", + pull_requests=("#60567", "#61696"), + ), + _source( + "DORIS_RELEASE_4_1_2", + FeatureSourceKind.RELEASE_NOTE, + "Apache Doris 4.1.2 Release Notes", + "https://doris.apache.org/releases/v4.1/release-4.1.2/", + ">=4.1.2", + pull_requests=("#61819", "#62077", "#63206"), + ), + _source( + "DORIS_RELEASE_4_1_3", + FeatureSourceKind.RELEASE_NOTE, + "Apache Doris 4.1.3 Release Notes", + "https://doris.apache.org/releases/v4.1/release-4.1.3/", + ">=4.1.3", + pull_requests=("#63387",), + ), + _source( + "DORIS_LINEAGE_GUIDE", + FeatureSourceKind.DOCUMENTATION, + "Apache Doris Data Lineage Technical Guide", + "https://doris.apache.org/docs/4.x/data-governance/data-lineage/", + ">=4.0.6", + pull_requests=("#61004",), + ), + _source( + "DORIS_LINEAGE_PLUGIN_GUIDE", + FeatureSourceKind.DOCUMENTATION, + "Apache Doris Lineage Plugin Development Guide", + ( + "https://doris.apache.org/community/developer-guide/" + "data-lineage-plugin-development/" + ), + ">=4.0.6", + pull_requests=("#61004",), + ), + _source( + "ARROW_ADBC_FLIGHT_SQL", + FeatureSourceKind.PROVIDER_SPECIFICATION, + "Apache Arrow ADBC Flight SQL Driver", + "https://arrow.apache.org/adbc/current/driver/flight_sql.html", + PROJECT_SUPPORTED_RANGE, + ), + _source( + "APACHE_OSSIE_CORE_SPEC", + FeatureSourceKind.PROVIDER_SPECIFICATION, + "Apache Ossie Core Specification", + ( + "https://github.com/apache/ossie/blob/" + "9ffc3be3886e82fddc9bbf28722440864644d371/core-spec/spec.md" + ), + PROJECT_SUPPORTED_RANGE, + ), +) + + +def _variant( + name: str, + *, + ranges: tuple[str, ...] = (PROJECT_SUPPORTED_RANGE,), + excluded_ranges: tuple[str, ...] = (), + deployment_modes: tuple[str, ...] = (), + features: tuple[str, ...] = (), + system_objects: tuple[str, ...] = (), + endpoints: tuple[str, ...] = (), + providers: tuple[str, ...] = (), + probes: tuple[str, ...], + evidence_quality: str = "native", + callable_when_degraded: bool = False, + sources: tuple[str, ...] = ("DORIS_DOCS_3X", "DORIS_DOCS_4X"), +) -> CapabilityVariant: + return CapabilityVariant( + name=name, + supported_ranges=ranges, + excluded_ranges=excluded_ranges, + deployment_modes=deployment_modes, + required_features=features, + required_system_objects=system_objects, + required_endpoints=endpoints, + required_providers=providers, + required_probes=probes, + evidence_quality=evidence_quality, + callable_when_degraded=callable_when_degraded, + source_references=sources, + ) + + +def _feature( + domain: str, + child_name: str, + scope: CapabilityVersionScope, + *variants: CapabilityVariant, +) -> ChildFeatureDefinition: + return ChildFeatureDefinition( + domain=domain, + child_name=child_name, + version_scope=scope, + support_contract=ChildSupportContract( + rule_id=f"{domain}.{child_name}", + variants=variants, + unavailable_behavior=UnavailableBehavior.SHOW_DISABLED, + unknown_is_callable=False, + tested_versions=(), + ), + ) + + +M = CapabilityVersionScope.MASTER_FE +F = CapabilityVersionScope.ALL_FE +B = CapabilityVersionScope.ALL_BE +A = CapabilityVersionScope.ALL_COMPONENTS +P = CapabilityVersionScope.PROVIDER_WITH_MASTER_FE_BASELINE + +FEATURE_DEFINITIONS = ( + _feature( + "doris_catalog", + "list_catalogs", + M, + _variant("catalog_metadata", probes=("catalog_metadata_readable",)), + ), + _feature( + "doris_catalog", + "list_databases", + M, + _variant("database_metadata", probes=("database_metadata_readable",)), + ), + _feature( + "doris_catalog", + "list_tables", + M, + _variant("table_metadata", probes=("table_metadata_readable",)), + ), + _feature( + "doris_catalog", + "get_table_context", + M, + _variant( + "base_sections", + system_objects=("information_schema.columns",), + probes=("table_context_sections_readable",), + ), + _variant( + "vector_search_metadata", + ranges=(">=4.0.0",), + features=("ann_index", "bm25_search"), + probes=("search_index_metadata_readable",), + sources=("DORIS_RELEASE_4_0_0",), + ), + _variant( + "variant_v3_metadata", + ranges=(">=4.1.0",), + features=("variant", "storage_v3"), + probes=("variant_table_metadata_readable",), + sources=("DORIS_RELEASE_4_1_0",), + ), + ), + _feature( + "doris_catalog", + "get_table_size", + A, + _variant( + "partition_statistics", + probes=("table_partition_statistics_readable",), + callable_when_degraded=True, + ), + ), + _feature( + "doris_query", + "execute_query", + A, + _variant( + "mysql_read_only", + probes=("read_only_sql_guard_ready", "query_execution_readable"), + ), + ), + _feature( + "doris_query", + "explain_query", + A, + _variant("base_explain", probes=("explain_output_readable",)), + _variant( + "search_plan_facets", + ranges=(">=4.0.0",), + features=("ann_index", "hybrid_search"), + probes=("search_plan_facets_readable",), + sources=("DORIS_RELEASE_4_0_0",), + ), + ), + _feature( + "doris_query", + "get_query_profile", + A, + _variant( + "query_profile", + endpoints=("fe_profile_api",), + probes=("query_profile_api_readable",), + ), + ), + _feature( + "doris_query", + "diagnose_query_performance", + A, + _variant( + "deterministic_query_diagnosis", + providers=("query_evidence_provider",), + probes=("explain_readable", "profile_or_audit_readable"), + evidence_quality="composite", + callable_when_degraded=True, + ), + ), + _feature( + "doris_query", + "list_slow_queries", + M, + _variant( + "audit_slow_queries", + providers=("audit_log_provider",), + probes=("audit_log_readable",), + evidence_quality="recorded", + ), + ), + _feature( + "doris_query", + "get_adbc_connection_info", + P, + _variant( + "adbc_flight_sql", + endpoints=("flight_sql",), + providers=("adbc_provider",), + probes=("adbc_driver_ready", "flight_sql_reachable"), + sources=("ARROW_ADBC_FLIGHT_SQL",), + ), + ), + _feature( + "doris_query", + "execute_adbc_query", + P, + _variant( + "adbc_read_only", + endpoints=("flight_sql",), + providers=("adbc_provider",), + probes=("adbc_driver_ready", "read_only_sql_guard_ready"), + sources=("ARROW_ADBC_FLIGHT_SQL",), + ), + ), + _feature( + "doris_cluster", + "get_cluster_overview", + A, + _variant( + "cluster_summary", + probes=("cluster_components_discoverable",), + evidence_quality="composite", + callable_when_degraded=True, + ), + ), + _feature( + "doris_cluster", + "list_cluster_nodes", + A, + _variant("cluster_nodes", probes=("fe_be_node_metadata_readable",)), + ), + _feature( + "doris_cluster", + "list_active_tasks", + A, + _variant( + "legacy_task_views", + probes=("legacy_task_views_readable",), + callable_when_degraded=True, + ), + _variant( + "unified_task_progress", + ranges=(">=4.1.1",), + system_objects=("information_schema.active_tasks",), + probes=("unified_task_progress_readable",), + sources=("DORIS_RELEASE_4_1_1",), + ), + ), + _feature( + "doris_cluster", + "get_monitoring_metrics", + A, + _variant( + "base_metrics", + endpoints=("fe_metrics", "be_metrics"), + probes=("metrics_endpoints_readable",), + ), + _variant( + "observability_4_0_7", + ranges=(">=4.0.7,<4.1.0",), + features=( + "connection_limit_metrics", + "routine_load_metrics", + "file_cache_queue_metrics", + ), + probes=("enhanced_metrics_present",), + sources=("DORIS_RELEASE_4_0_7",), + ), + ), + _feature( + "doris_cluster", + "get_memory_stats", + A, + _variant( + "memory_trackers", + endpoints=("be_metrics",), + probes=("memory_tracker_metrics_readable",), + callable_when_degraded=True, + ), + ), + _feature( + "doris_cluster", + "get_cache_status", + B, + _variant( + "file_cache", + endpoints=("be_metrics",), + probes=("file_cache_metrics_readable",), + ), + _variant( + "file_cache_queue_metrics", + ranges=(">=4.0.7,<4.1.0",), + probes=("file_cache_queue_metrics_present",), + sources=("DORIS_RELEASE_4_0_7",), + ), + _variant( + "advanced_cache_types", + ranges=(">=4.1.0",), + features=("condition_cache", "parquet_page_cache"), + probes=("advanced_cache_metrics_present",), + sources=("DORIS_RELEASE_4_1_0",), + ), + ), + _feature( + "doris_cluster", + "get_compaction_status", + B, + _variant( + "legacy_compaction_summary", + probes=("legacy_compaction_status_readable",), + evidence_quality="summary", + callable_when_degraded=True, + ), + _variant( + "compaction_task_tracker", + ranges=(">=4.0.6,<4.1.0", ">=4.1.1"), + excluded_ranges=(">=4.1.0-alpha0,<4.1.1",), + endpoints=("be_compaction_api",), + probes=("compaction_system_table_or_http_api",), + sources=("DORIS_RELEASE_4_0_6", "DORIS_RELEASE_4_1_1"), + ), + ), + _feature( + "doris_cluster", + "get_workload_group_status", + A, + _variant( + "workload_group_metrics", + probes=("workload_group_metadata_and_metrics_readable",), + ), + ), + _feature( + "doris_cluster", + "get_compute_group_status", + A, + _variant( + "compute_group", + deployment_modes=("cloud", "shared_data"), + probes=("compute_group_metadata_readable",), + ), + ), + _feature( + "doris_cluster", + "analyze_resource_growth", + A, + _variant( + "historical_resource_metrics", + providers=("metrics_history_provider",), + probes=("metrics_history_readable",), + evidence_quality="recorded", + ), + ), + _feature( + "doris_cluster", + "get_runtime_capabilities", + M, + _variant( + "capability_snapshot", + probes=("version_probe_completed",), + evidence_quality="detected", + ), + ), + _feature( + "doris_pipeline", + "get_ingestion_status", + A, + _variant( + "load_jobs", + probes=("stream_broker_routine_load_readable",), + ), + _variant( + "continuous_load", + ranges=(">=4.1.0",), + probes=("continuous_load_metadata_readable",), + sources=("DORIS_RELEASE_4_1_0",), + ), + ), + _feature( + "doris_pipeline", + "diagnose_ingestion", + A, + _variant( + "deterministic_ingestion_diagnosis", + providers=("ingestion_evidence_provider",), + probes=("ingestion_status_readable", "freshness_evidence_readable"), + evidence_quality="composite", + callable_when_degraded=True, + ), + ), + _feature( + "doris_pipeline", + "get_materialized_view_status", + A, + _variant( + "materialized_view_metadata", + probes=("materialized_view_tasks_readable",), + ), + _variant( + "mtmv_compute_group", + ranges=(">=4.0.7,<4.1.0", ">=4.1.2"), + deployment_modes=("cloud", "shared_data"), + probes=("mtmv_compute_group_present",), + sources=("DORIS_RELEASE_4_0_7", "DORIS_RELEASE_4_1_2"), + ), + ), + _feature( + "doris_pipeline", + "monitor_data_freshness", + A, + _variant( + "freshness_evidence", + probes=("load_offset_or_time_evidence_readable",), + callable_when_degraded=True, + ), + ), + _feature( + "doris_pipeline", + "analyze_data_dependencies", + M, + _variant( + "audit_sql_dependencies", + providers=("audit_log_provider",), + probes=("audit_log_readable",), + evidence_quality="inferred", + callable_when_degraded=True, + ), + ), + _feature( + "doris_search", + "search_data", + A, + _variant( + "inverted_text_search", + probes=("inverted_index_and_search_syntax_ready",), + ), + _variant( + "vector_hybrid_search", + ranges=(">=4.0.0",), + features=("ann_index", "hybrid_search"), + probes=("ann_index_and_metric_compatible",), + sources=("DORIS_RELEASE_4_0_0",), + ), + ), + _feature( + "doris_search", + "preview_text_analysis", + A, + _variant( + "tokenizer_preview", + probes=("tokenize_function_or_analyzer_ready",), + ), + ), + _feature( + "doris_search", + "inspect_search_indexes", + M, + _variant( + "inverted_index_metadata", + probes=("inverted_index_metadata_readable",), + ), + _variant( + "ann_index_metadata", + ranges=(">=4.0.0",), + features=("ann_index",), + probes=("ann_index_metadata_readable",), + sources=("DORIS_RELEASE_4_0_0",), + ), + ), + _feature( + "doris_search", + "diagnose_search_query", + A, + _variant( + "search_query_diagnosis", + providers=("search_evidence_provider",), + probes=("search_index_and_explain_readable",), + evidence_quality="composite", + callable_when_degraded=True, + ), + ), + _feature( + "doris_governance", + "analyze_columns", + A, + _variant( + "column_statistics", + probes=("column_statistics_and_safe_sampling_ready",), + ), + ), + _feature( + "doris_governance", + "analyze_table_storage", + A, + _variant( + "base_storage", + probes=("table_storage_metadata_readable",), + ), + _variant( + "storage_v3_variant", + ranges=(">=4.1.0",), + features=("storage_v3", "variant_sparse", "variant_doc"), + probes=("storage_v3_variant_properties_readable",), + sources=("DORIS_RELEASE_4_1_0",), + ), + ), + _feature( + "doris_governance", + "get_lineage_capability_status", + F, + _variant( + "audit_lineage_status", + providers=("audit_log_provider",), + probes=("audit_lineage_provider_status_readable",), + evidence_quality="inferred", + callable_when_degraded=True, + ), + _variant( + "native_lineage_status", + ranges=(">=4.0.6",), + providers=("lineage_event_store",), + probes=( + "all_fe_lineage_plugin_compatible", + "lineage_store_readable", + ), + sources=( + "DORIS_RELEASE_4_0_6", + "DORIS_LINEAGE_GUIDE", + "DORIS_LINEAGE_PLUGIN_GUIDE", + ), + ), + ), + _feature( + "doris_governance", + "trace_column_lineage", + F, + _variant( + "audit_sql_inference_primary", + ranges=(">=3.0.0,<4.0.6",), + providers=("audit_log_provider",), + probes=("audit_log_readable",), + evidence_quality="inferred", + callable_when_degraded=True, + ), + _variant( + "native_lineage_events", + ranges=(">=4.0.6",), + providers=("lineage_event_store",), + probes=( + "all_fe_lineage_plugin_compatible", + "lineage_store_readable", + ), + sources=( + "DORIS_RELEASE_4_0_6", + "DORIS_LINEAGE_GUIDE", + "DORIS_LINEAGE_PLUGIN_GUIDE", + ), + ), + _variant( + "audit_sql_inference_fallback", + ranges=(">=4.0.6",), + providers=("audit_log_provider",), + probes=("audit_log_readable",), + evidence_quality="inferred", + callable_when_degraded=True, + sources=("DORIS_LINEAGE_GUIDE", "DORIS_DOCS_4X"), + ), + ), + _feature( + "doris_governance", + "analyze_data_access_patterns", + M, + _variant( + "audit_access_history", + providers=("audit_log_provider",), + probes=("audit_history_readable",), + evidence_quality="recorded", + ), + ), + _feature( + "doris_governance", + "get_recent_audit_logs", + M, + _variant( + "audit_log", + providers=("audit_log_provider",), + probes=("audit_log_readable",), + ), + _variant( + "audit_log_4_0_7", + ranges=(">=4.0.7,<4.1.0",), + features=("set_var_audit", "enhanced_secret_masking"), + probes=("enhanced_audit_fields_present",), + sources=("DORIS_RELEASE_4_0_7",), + ), + ), + _feature( + "doris_governance", + "list_udfs", + M, + _variant("base_udf_metadata", probes=("udf_metadata_readable",)), + _variant( + "python_udf_family", + ranges=(">=4.1.3",), + features=("python_udf", "python_udaf", "python_udtf"), + probes=("python_udf_metadata_readable",), + sources=("DORIS_RELEASE_4_1_3",), + ), + ), + _feature( + "doris_governance", + "get_auth_mapping_status", + F, + _variant( + "ldap_mapping", + providers=("ldap_auth_provider",), + probes=("ldap_mapping_status_readable",), + ), + _variant( + "oidc_role_mapping", + ranges=(">=4.1.2",), + providers=("oidc_auth_provider",), + system_objects=("information_schema.role_mappings",), + probes=("oidc_and_role_mapping_status_readable",), + sources=("DORIS_RELEASE_4_1_2",), + ), + ), + _feature( + "doris_lakehouse", + "inspect_external_catalog", + M, + _variant( + "external_catalog_metadata", + providers=("external_catalog_provider",), + probes=("external_catalog_metadata_readable",), + ), + ), + _feature( + "doris_lakehouse", + "inspect_lakehouse_table", + A, + _variant( + "lakehouse_table_metadata", + providers=("external_catalog_provider",), + probes=("lakehouse_table_metadata_readable",), + ), + _variant( + "lakehouse_lifecycle_4_1", + ranges=(">=4.1.0",), + features=("iceberg_deletion_vector", "iceberg_row_lineage"), + probes=("lakehouse_snapshot_features_readable",), + sources=("DORIS_RELEASE_4_1_0",), + ), + ), + _feature( + "doris_lakehouse", + "inspect_variant_column", + A, + _variant( + "variant_type", + probes=("variant_column_type_readable",), + ), + _variant( + "variant_advanced_4_1", + ranges=(">=4.1.0",), + features=( + "variant_sparse_sharding", + "variant_sparse_cache", + "variant_doc_mode", + "storage_v3", + ), + probes=("variant_advanced_properties_readable",), + sources=("DORIS_RELEASE_4_1_0",), + ), + ), + _feature( + "doris_semantic", + "list_semantic_models", + P, + _variant( + "ossie_model_registry", + providers=("ossie_provider",), + probes=("ossie_spec_and_registry_ready",), + sources=("APACHE_OSSIE_CORE_SPEC",), + ), + ), + _feature( + "doris_semantic", + "get_semantic_model_summary", + P, + _variant( + "ossie_model_summary", + providers=("ossie_provider",), + probes=("explicit_model_ref_valid", "ossie_model_valid"), + sources=("APACHE_OSSIE_CORE_SPEC",), + ), + ), + _feature( + "doris_semantic", + "get_semantic_context", + P, + _variant( + "ossie_semantic_context", + providers=("ossie_provider",), + probes=("explicit_model_ref_valid", "semantic_policy_ready"), + sources=("APACHE_OSSIE_CORE_SPEC",), + ), + ), + _feature( + "doris_semantic", + "get_semantic_mapping_status", + P, + _variant( + "ossie_doris_mapping", + providers=("ossie_provider",), + probes=( + "explicit_model_ref_valid", + "doris_binding_metadata_readable", + ), + sources=("APACHE_OSSIE_CORE_SPEC", "DORIS_DOCS_4X"), + ), + ), +) + +DORIS_FEATURE_MATRIX = DorisFeatureMatrix( + minimum_supported_version=PROJECT_MINIMUM_DORIS_VERSION, + certification_targets=CERTIFICATION_TARGET_VERSIONS, + certified_versions=CERTIFIED_DORIS_VERSIONS, + sources=FEATURE_SOURCES, + features=FEATURE_DEFINITIONS, +) + + +__all__ = [ + "CERTIFICATION_TARGET_VERSIONS", + "CERTIFIED_DORIS_VERSIONS", + "DORIS_FEATURE_MATRIX", + "EXPECTED_DOMAIN_CHILDREN", + "FEATURE_DEFINITIONS", + "FEATURE_SOURCES", + "PROJECT_MINIMUM_DORIS_VERSION", + "PROJECT_SUPPORTED_RANGE", + "CapabilityVersionScope", + "ChildFeatureDefinition", + "DorisClusterVersionVector", + "DorisFeatureMatrix", + "DorisVersionConstraint", + "DorisVersionRange", + "FeatureSource", + "FeatureSourceKind", + "FeatureVersionEvaluation", + "VersionCertificationStatus", + "VersionRangeOperator", + "matching_version_ranges", +] diff --git a/test/tools/test_doris_feature_matrix.py b/test/tools/test_doris_feature_matrix.py new file mode 100644 index 0000000..19c8eb9 --- /dev/null +++ b/test/tools/test_doris_feature_matrix.py @@ -0,0 +1,685 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from collections import Counter +from typing import Any, cast + +import pytest +from pydantic import ValidationError + +import doris_mcp_server.tools.doris_feature_matrix as matrix_module +from doris_mcp_server.tools.domain_models import CapabilityVariant +from doris_mcp_server.tools.doris_feature_matrix import ( + CERTIFICATION_TARGET_VERSIONS, + CERTIFIED_DORIS_VERSIONS, + DORIS_FEATURE_MATRIX, + EXPECTED_DOMAIN_CHILDREN, + PROJECT_MINIMUM_DORIS_VERSION, + CapabilityVersionScope, + DorisClusterVersionVector, + DorisFeatureMatrix, + DorisVersionRange, + FeatureSource, + FeatureSourceKind, + VersionCertificationStatus, + matching_version_ranges, +) +from doris_mcp_server.tools.doris_version import parse_doris_version_comment + + +def _vector( + version: str, + *, + followers: tuple[str, ...] = (), + backends: tuple[str, ...] | None = None, +) -> DorisClusterVersionVector: + raw = f"Doris version doris-{version}" + return DorisClusterVersionVector.from_comments( + master_fe=raw, + follower_fes=tuple(f"Doris version doris-{item}" for item in followers), + backends=tuple( + f"Doris version doris-{item}" + for item in (backends if backends is not None else (version,)) + ), + ) + + +def _matrix_payload() -> dict[str, Any]: + return cast(dict[str, Any], DORIS_FEATURE_MATRIX.model_dump(mode="python")) + + +def test_matrix_contains_exact_8_domain_47_child_contract() -> None: + assert len(DORIS_FEATURE_MATRIX.features) == 47 + assert tuple(EXPECTED_DOMAIN_CHILDREN) == ( + "doris_catalog", + "doris_query", + "doris_cluster", + "doris_pipeline", + "doris_search", + "doris_governance", + "doris_lakehouse", + "doris_semantic", + ) + assert Counter(feature.domain for feature in DORIS_FEATURE_MATRIX.features) == { + "doris_catalog": 5, + "doris_query": 7, + "doris_cluster": 11, + "doris_pipeline": 5, + "doris_search": 4, + "doris_governance": 8, + "doris_lakehouse": 3, + "doris_semantic": 4, + } + for domain, expected_children in EXPECTED_DOMAIN_CHILDREN.items(): + actual_children = tuple( + feature.child_name + for feature in DORIS_FEATURE_MATRIX.features + if feature.domain == domain + ) + assert actual_children == expected_children + + +def test_adbc_is_inside_query_and_variant_is_inside_lakehouse() -> None: + query_children = EXPECTED_DOMAIN_CHILDREN["doris_query"] + lakehouse_children = EXPECTED_DOMAIN_CHILDREN["doris_lakehouse"] + + assert "get_adbc_connection_info" in query_children + assert "execute_adbc_query" in query_children + assert "inspect_variant_column" in lakehouse_children + assert "doris_adbc" not in EXPECTED_DOMAIN_CHILDREN + + +def test_every_contract_is_fail_closed_and_has_resolvable_sources() -> None: + known_sources = {source.source_id for source in DORIS_FEATURE_MATRIX.sources} + + for feature in DORIS_FEATURE_MATRIX.features: + contract = feature.support_contract + assert contract.rule_id == feature.feature_id + assert contract.unknown_is_callable is False + assert contract.unavailable_behavior.value == "show_disabled" + assert contract.tested_versions == () + for variant in contract.variants: + assert variant.source_references + assert set(variant.source_references) <= known_sources + assert variant.required_probes + + +def test_sources_are_https_authorities_with_branch_ranges() -> None: + for source in DORIS_FEATURE_MATRIX.sources: + assert source.url.startswith("https://") + assert source.applicable_ranges + assert source.verified_on.isoformat() == "2026-07-31" + for expression in source.applicable_ranges: + assert DorisVersionRange(expression).expression == expression + + release_406 = DORIS_FEATURE_MATRIX.get_source("DORIS_RELEASE_4_0_6") + assert release_406.pull_requests == ("#61004", "#61696") + assert release_406.applicable_ranges == (">=4.0.6,<4.1.0",) + + release_412 = DORIS_FEATURE_MATRIX.get_source("DORIS_RELEASE_4_1_2") + assert {"#61819", "#62077", "#63206"} == set(release_412.pull_requests) + + +def test_certification_targets_are_not_claimed_as_certified() -> None: + assert PROJECT_MINIMUM_DORIS_VERSION == "3.0.0" + assert DORIS_FEATURE_MATRIX.certification_targets == (CERTIFICATION_TARGET_VERSIONS) + assert DORIS_FEATURE_MATRIX.certified_versions == () + assert CERTIFIED_DORIS_VERSIONS == () + assert { + "DORIS_RELEASE_3_0_3", + "DORIS_RELEASE_3_1_4", + "DORIS_RELEASE_4_0_5", + "DORIS_RELEASE_4_1_3", + } <= {source.source_id for source in DORIS_FEATURE_MATRIX.sources} + + [email protected]( + ("expression", "matching", "not_matching"), + [ + (">=3.0.0", "4.1.3", "3.0.0-rc01"), + (">=4.0.6,<4.1.0", "4.0.7", "4.1.0"), + ("==4.1.0", "4.1.0", "4.1.1"), + (">4.1.0", "4.1.1", "4.1.0"), + ("<=4.0.6", "4.0.6", "4.0.7"), + ("<4.0.6", "4.0.6-rc02", "4.0.6"), + ], +) +def test_version_range_operators( + expression: str, + matching: str, + not_matching: str, +) -> None: + version_range = DorisVersionRange(expression) + + assert version_range.contains( + parse_doris_version_comment(f"Doris version doris-{matching}") + ) + assert not version_range.contains( + parse_doris_version_comment(f"Doris version doris-{not_matching}") + ) + + +def test_version_range_normalizes_spacing() -> None: + version_range = DorisVersionRange(" >=4.0.6 , <4.1.0 ") + + assert version_range.expression == ">=4.0.6,<4.1.0" + + [email protected]( + "expression", + [ + "", + "4.0.6", + "=>4.0.6", + ">=4.0", + ">=4.0.6,", + ">=4.0.6,<4.0.6", + ">=4.1.0,<4.0.0", + "==4.0.6,==4.0.7", + "==4.0.6,>4.0.6", + ], +) +def test_invalid_or_contradictory_ranges_are_rejected( + expression: str, +) -> None: + with pytest.raises(ValueError): + DorisVersionRange(expression) + + +def test_equal_bounds_can_be_inclusive_and_choose_stricter_duplicates() -> None: + inclusive = DorisVersionRange(">=4.0.6,<=4.0.6") + stricter_lower = DorisVersionRange(">=4.0.6,>4.0.6,<4.1.0") + stricter_upper = DorisVersionRange("<=4.1.0,<4.1.0,>=4.0.0") + + stable_406 = parse_doris_version_comment("Doris version doris-4.0.6") + stable_407 = parse_doris_version_comment("Doris version doris-4.0.7") + stable_410 = parse_doris_version_comment("Doris version doris-4.1.0") + + assert inclusive.contains(stable_406) + assert not stricter_lower.contains(stable_406) + assert stricter_lower.contains(stable_407) + assert not stricter_upper.contains(stable_410) + + +def test_unknown_version_never_matches_a_range() -> None: + unknown = parse_doris_version_comment("Doris version unknown") + + assert DorisVersionRange(">=3.0.0").contains(unknown) is False + + +def test_version_range_orders_rc_before_stable_and_ignores_commit() -> None: + stable_range = DorisVersionRange(">=4.0.6") + release_candidate = parse_doris_version_comment( + "Doris version doris-4.0.6-rc03-43f06a5e26" + ) + stable_with_commit = parse_doris_version_comment( + "Doris version doris-4.0.6-abcdef1234" + ) + + assert stable_range.contains(release_candidate) is False + assert stable_range.contains(stable_with_commit) is True + assert DorisVersionRange("==4.0.6").contains(stable_with_commit) is True + + +def test_variant_exclusions_override_supported_ranges() -> None: + variant = CapabilityVariant( + name="branch_feature", + supported_ranges=(">=4.0.0",), + excluded_ranges=(">=4.1.0,<4.1.1",), + required_probes=("branch_feature_probe",), + ) + + assert matching_version_ranges( + variant, + parse_doris_version_comment("Doris version doris-4.0.7"), + ) == (">=4.0.0",) + assert ( + matching_version_ranges( + variant, + parse_doris_version_comment("Doris version doris-4.1.0"), + ) + == () + ) + assert ( + matching_version_ranges( + variant, + parse_doris_version_comment("Doris version unknown"), + ) + == () + ) + + [email protected]( + ("version", "expected"), + [ + ("4.0.5", False), + ("4.0.6", True), + ("4.0.7", True), + ("4.1.0-rc01", False), + ("4.1.0", False), + ("4.1.1", True), + ("4.1.3", True), + ], +) +def test_compaction_tracker_uses_non_monotonic_maintenance_ranges( + version: str, + expected: bool, +) -> None: + result = DORIS_FEATURE_MATRIX.evaluate( + domain="doris_cluster", + child_name="get_compaction_status", + versions=_vector(version), + variant_name="compaction_task_tracker", + ) + + assert result.compatible is expected + assert result.reason_code == ( + "VERSION_RANGE_MATCHED" if expected else "VERSION_RANGE_NOT_MATCHED" + ) + if expected: + assert result.matched_ranges in { + (">=4.0.6,<4.1.0",), + (">=4.1.1",), + } + else: + assert result.matched_ranges == () + assert { + "DORIS_RELEASE_4_0_6", + "DORIS_RELEASE_4_1_1", + } <= set(result.source_ids) + + +def test_compaction_child_keeps_a_degraded_legacy_variant() -> None: + result = DORIS_FEATURE_MATRIX.evaluate( + domain="doris_cluster", + child_name="get_compaction_status", + versions=_vector("4.0.5"), + ) + + assert result.compatible is True + assert result.matched_variants == ("legacy_compaction_summary",) + assert result.certification_status is ( + VersionCertificationStatus.TARGET_UNCERTIFIED + ) + assert result.certification_target is True + assert result.certified is False + + +def test_lineage_native_and_audit_paths_are_both_explicit() -> None: + feature = DORIS_FEATURE_MATRIX.get_feature( + "doris_governance", + "trace_column_lineage", + ) + variants = {variant.name: variant for variant in feature.support_contract.variants} + + assert variants["audit_sql_inference_primary"].supported_ranges == ( + ">=3.0.0,<4.0.6", + ) + assert variants["native_lineage_events"].supported_ranges == (">=4.0.6",) + assert variants["audit_sql_inference_fallback"].supported_ranges == (">=4.0.6",) + assert variants["audit_sql_inference_fallback"].callable_when_degraded is True + + [email protected]( + ("version", "expected"), + [ + ("4.1.0", False), + ("4.1.1", False), + ("4.1.2", False), + ("4.1.3-rc01", False), + ("4.1.3", True), + ], +) +def test_python_udf_family_starts_at_4_1_3( + version: str, + expected: bool, +) -> None: + result = DORIS_FEATURE_MATRIX.evaluate( + domain="doris_governance", + child_name="list_udfs", + versions=_vector(version), + variant_name="python_udf_family", + ) + + assert result.compatible is expected + assert result.source_ids == ("DORIS_RELEASE_4_1_3",) + + +def test_native_lineage_requires_every_fe_to_reach_4_0_6() -> None: + mixed = DORIS_FEATURE_MATRIX.evaluate( + domain="doris_governance", + child_name="trace_column_lineage", + versions=_vector("4.0.7", followers=("4.0.5",)), + variant_name="native_lineage_events", + ) + uniform = DORIS_FEATURE_MATRIX.evaluate( + domain="doris_governance", + child_name="trace_column_lineage", + versions=_vector("4.0.7", followers=("4.0.6",)), + variant_name="native_lineage_events", + ) + + assert mixed.version_scope is CapabilityVersionScope.ALL_FE + assert mixed.effective_version == "4.0.5" + assert mixed.compatible is False + assert uniform.effective_version == "4.0.6" + assert uniform.compatible is True + + +def test_mixed_fe_be_patch_uses_conservative_all_component_version() -> None: + result = DORIS_FEATURE_MATRIX.evaluate( + domain="doris_cluster", + child_name="list_active_tasks", + versions=_vector( + "4.1.2", + followers=("4.1.1",), + backends=("4.0.7", "4.1.2"), + ), + variant_name="unified_task_progress", + ) + + assert result.version_scope is CapabilityVersionScope.ALL_COMPONENTS + assert result.raw_version == "Doris version doris-4.0.7" + assert result.effective_version == "4.0.7" + assert result.compatible is False + + +def test_master_fe_scope_does_not_inherit_an_unrelated_old_be_version() -> None: + result = DORIS_FEATURE_MATRIX.evaluate( + domain="doris_catalog", + child_name="list_tables", + versions=_vector("4.1.2", backends=("3.0.3",)), + ) + + assert result.version_scope is CapabilityVersionScope.MASTER_FE + assert result.effective_version == "4.1.2" + assert result.compatible is True + + +def test_missing_required_component_version_fails_closed() -> None: + versions = DorisClusterVersionVector.from_comments( + master_fe="Doris version doris-4.1.2", + backends=(), + ) + result = DORIS_FEATURE_MATRIX.evaluate( + domain="doris_cluster", + child_name="get_cache_status", + versions=versions, + ) + + assert result.compatible is False + assert result.effective_version is None + assert result.reason_code == "DORIS_VERSION_UNKNOWN" + assert result.certification_status is VersionCertificationStatus.UNKNOWN + + +def test_all_component_scope_requires_at_least_one_backend_observation() -> None: + result = DORIS_FEATURE_MATRIX.evaluate( + domain="doris_cluster", + child_name="list_active_tasks", + versions=DorisClusterVersionVector.from_comments( + master_fe="Doris version doris-4.1.2", + backends=(), + ), + variant_name="unified_task_progress", + ) + + assert result.compatible is False + assert result.reason_code == "DORIS_VERSION_UNKNOWN" + assert result.raw_version == "Doris version doris-4.1.2" + assert result.observed_fe_version_comments == ("Doris version doris-4.1.2",) + assert result.observed_be_version_comments == () + + +def test_an_unparseable_component_version_fails_closed() -> None: + result = DORIS_FEATURE_MATRIX.evaluate( + domain="doris_governance", + child_name="get_lineage_capability_status", + versions=DorisClusterVersionVector.from_comments( + master_fe="Doris version doris-4.0.7", + follower_fes=("Doris version unknown",), + ), + variant_name="native_lineage_status", + ) + + assert result.compatible is False + assert result.reason_code == "DORIS_VERSION_UNKNOWN" + assert result.raw_version == "Doris version doris-4.0.7" + assert result.observed_fe_version_comments == ( + "Doris version doris-4.0.7", + "Doris version unknown", + ) + + +def test_version_below_3_0_0_is_rejected_before_child_evaluation() -> None: + result = DORIS_FEATURE_MATRIX.evaluate( + domain="doris_catalog", + child_name="list_tables", + versions=_vector("2.1.8"), + ) + + assert result.compatible is False + assert result.reason_code == "DORIS_VERSION_BELOW_MINIMUM" + assert result.minimum_supported_version == "3.0.0" + + +def test_target_patch_is_distinct_from_certified_patch() -> None: + result = DORIS_FEATURE_MATRIX.evaluate( + domain="doris_catalog", + child_name="list_tables", + versions=_vector("4.1.2"), + ) + + assert result.compatible is True + assert result.certification_status is ( + VersionCertificationStatus.TARGET_UNCERTIFIED + ) + assert result.certification_target is True + assert result.certified is False + + +def test_version_outside_initial_certification_targets_is_not_certified() -> None: + result = DORIS_FEATURE_MATRIX.evaluate( + domain="doris_catalog", + child_name="list_tables", + versions=_vector("4.1.4"), + ) + + assert result.compatible is True + assert result.certification_status is VersionCertificationStatus.OUTSIDE_TARGET + assert result.certification_target is False + assert result.certified is False + + +def test_semantic_children_require_explicit_model_reference_probes() -> None: + for child_name in ( + "get_semantic_model_summary", + "get_semantic_context", + "get_semantic_mapping_status", + ): + feature = DORIS_FEATURE_MATRIX.get_feature( + "doris_semantic", + child_name, + ) + assert "explicit_model_ref_valid" in ( + feature.support_contract.variants[0].required_probes + ) + + +def test_unknown_feature_source_and_feature_lookups_raise_key_error() -> None: + with pytest.raises(KeyError, match="DORIS_NOT_REAL"): + DORIS_FEATURE_MATRIX.get_source("DORIS_NOT_REAL") + with pytest.raises(KeyError, match="doris_catalog.not_real"): + DORIS_FEATURE_MATRIX.get_feature("doris_catalog", "not_real") + with pytest.raises(KeyError, match="not_real"): + DORIS_FEATURE_MATRIX.evaluate( + domain="doris_catalog", + child_name="list_tables", + versions=_vector("4.1.2"), + variant_name="not_real", + ) + + +def test_matrix_rejects_an_unknown_source_reference() -> None: + payload = _matrix_payload() + payload["features"][0]["support_contract"]["variants"][0]["source_references"] = ( + "DORIS_NOT_REAL", + ) + + with pytest.raises(ValidationError, match="unknown source IDs"): + DorisFeatureMatrix.model_validate(payload) + + +def test_matrix_rejects_a_variant_without_source_ids() -> None: + payload = _matrix_payload() + payload["features"][0]["support_contract"]["variants"][0]["source_references"] = () + + with pytest.raises(ValidationError, match="requires source IDs"): + DorisFeatureMatrix.model_validate(payload) + + +def test_matrix_rejects_missing_or_reordered_children() -> None: + payload = _matrix_payload() + payload["features"] = payload["features"][:-1] + + with pytest.raises(ValidationError, match="8-domain/47-child"): + DorisFeatureMatrix.model_validate(payload) + + +def test_matrix_rejects_invalid_contract_range() -> None: + payload = _matrix_payload() + payload["features"][0]["support_contract"]["variants"][0]["supported_ranges"] = ( + ">=4.0", + ) + + with pytest.raises(ValueError, match="version literal"): + DorisFeatureMatrix.model_validate(payload) + + +def test_matrix_rejects_certification_outside_target_set() -> None: + payload = _matrix_payload() + payload["certified_versions"] = ("4.2.0",) + + with pytest.raises(ValidationError, match="must be certification targets"): + DorisFeatureMatrix.model_validate(payload) + + +def test_matrix_rejects_a_prerelease_minimum() -> None: + payload = _matrix_payload() + payload["minimum_supported_version"] = "3.0.0-rc01" + + with pytest.raises(ValidationError, match="minimum supported version"): + DorisFeatureMatrix.model_validate(payload) + + +def test_a_real_certified_version_is_distinct_from_target_only() -> None: + payload = _matrix_payload() + payload["certified_versions"] = ("4.1.2",) + matrix = DorisFeatureMatrix.model_validate(payload) + + result = matrix.evaluate( + domain="doris_catalog", + child_name="list_tables", + versions=_vector("4.1.2"), + ) + + assert result.certification_status is VersionCertificationStatus.CERTIFIED + assert result.certification_target is False + assert result.certified is True + + +def test_matrix_rejects_duplicate_sources_and_features() -> None: + source_payload = _matrix_payload() + source_payload["sources"] = ( + *source_payload["sources"], + source_payload["sources"][0], + ) + with pytest.raises(ValidationError, match="feature source IDs"): + DorisFeatureMatrix.model_validate(source_payload) + + feature_payload = _matrix_payload() + feature_payload["features"] = ( + feature_payload["features"][0], + feature_payload["features"][0], + *feature_payload["features"][2:], + ) + with pytest.raises(ValidationError, match="feature IDs"): + DorisFeatureMatrix.model_validate(feature_payload) + + +def test_matrix_rejects_certification_and_rule_id_duplicates() -> None: + target_payload = _matrix_payload() + target_payload["certification_targets"] = ("4.1.2", "4.1.2") + with pytest.raises(ValidationError, match="certification targets"): + DorisFeatureMatrix.model_validate(target_payload) + + rule_payload = _matrix_payload() + rule_payload["features"][1]["support_contract"]["rule_id"] = rule_payload[ + "features" + ][0]["support_contract"]["rule_id"] + with pytest.raises(ValidationError): + DorisFeatureMatrix.model_validate(rule_payload) + + +def test_source_rejects_non_https_and_invalid_range() -> None: + with pytest.raises(ValidationError, match="must use HTTPS"): + FeatureSource( + source_id="TEST_SOURCE", + kind=FeatureSourceKind.DOCUMENTATION, + title="Test source", + url="http://example.test/docs", + applicable_ranges=(">=3.0.0",), + verified_on="2026-07-31", + ) + + with pytest.raises(ValueError, match="version literal"): + FeatureSource( + source_id="TEST_SOURCE", + kind=FeatureSourceKind.DOCUMENTATION, + title="Test source", + url="https://example.test/docs", + applicable_ranges=(">=3.0",), + verified_on="2026-07-31", + ) + + +def test_defensive_literal_checks_reject_unparsed_versions( + monkeypatch: pytest.MonkeyPatch, +) -> None: + unknown = parse_doris_version_comment("Doris version unknown") + + with pytest.raises(ValueError, match="Cannot format"): + matrix_module._version_literal(unknown) + + monkeypatch.setattr( + matrix_module, + "parse_doris_version_comment", + lambda _comment: unknown, + ) + with pytest.raises(ValueError, match="Invalid Doris version literal"): + matrix_module._parse_version_literal("4.1.2") + + +def test_matrix_wire_serialization_is_stable_and_immutable() -> None: + first = DORIS_FEATURE_MATRIX.to_canonical_json() + second = DORIS_FEATURE_MATRIX.to_canonical_json() + + assert first == second + assert '"minimum_supported_version":"3.0.0"' in first + with pytest.raises(ValidationError): + DORIS_FEATURE_MATRIX.minimum_supported_version = "4.0.0" --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
