Commit f1bc115ae8 for bind

commit f1bc115ae8a461cdc6738f9f2a1888388ea46859
Author: Štěpán Balážik <stepan@isc.org>
Date:   Tue Sep 8 14:35:14 2026 +0200

    Add composable query matchers

    A matcher is a predicate over a query; matchers combine with &, |
    and ~ into larger ones.  Nothing uses them yet: the response handlers
    switch to declaring one next.

    Assisted-by: Claude:claude-fable-5

diff --git a/bin/tests/system/isctest/asyncserver/matchers.py b/bin/tests/system/isctest/asyncserver/matchers.py
new file mode 100644
index 0000000000..27ece151a7
--- /dev/null
+++ b/bin/tests/system/isctest/asyncserver/matchers.py
@@ -0,0 +1,108 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+import abc
+
+from .context import QueryContext
+
+
+class Matcher(abc.ABC):
+    """
+    A predicate over queries, deciding which ones a response handler handles.
+
+    Matchers are combined with `&`, `|` and `~`; a handler declares the result
+    in its `matcher` attribute.
+    """
+
+    @abc.abstractmethod
+    def match(self, qctx: QueryContext) -> bool:
+        """
+        Whether the query in `qctx` matches.
+        """
+        raise NotImplementedError
+
+    def __and__(self, other: "Matcher") -> "Matcher":
+        return AllOf(self, other)
+
+    def __or__(self, other: "Matcher") -> "Matcher":
+        return AnyOf(self, other)
+
+    def __invert__(self) -> "Matcher":
+        return Not(self)
+
+    def __str__(self) -> str:
+        return f"{self.__class__.__name__}()"
+
+
+class _Combinator(Matcher):
+    """
+    Base class for matchers combining other matchers.
+    """
+
+    _SEPARATOR = ", "
+
+    def __init__(self, *matchers: Matcher) -> None:
+        self._matchers = matchers
+
+    def __str__(self) -> str:
+        return f"({self._SEPARATOR.join(str(m) for m in self._matchers)})"
+
+
+class AllOf(_Combinator):
+    """
+    Match queries matched by every one of the given matchers (`a & b`).
+    """
+
+    _SEPARATOR = " and "
+
+    def __and__(self, other: Matcher) -> Matcher:
+        return AllOf(*self._matchers, other)
+
+    def match(self, qctx: QueryContext) -> bool:
+        return all(matcher.match(qctx) for matcher in self._matchers)
+
+
+class AnyOf(_Combinator):
+    """
+    Match queries matched by at least one of the given matchers (`a | b`).
+    """
+
+    _SEPARATOR = " or "
+
+    def __or__(self, other: Matcher) -> Matcher:
+        return AnyOf(*self._matchers, other)
+
+    def match(self, qctx: QueryContext) -> bool:
+        return any(matcher.match(qctx) for matcher in self._matchers)
+
+
+class Not(Matcher):
+    """
+    Match queries which the given matcher does not match (`~a`).
+    """
+
+    def __init__(self, matcher: Matcher) -> None:
+        self._matcher = matcher
+
+    def match(self, qctx: QueryContext) -> bool:
+        return not self._matcher.match(qctx)
+
+    def __str__(self) -> str:
+        return f"not {self._matcher}"
+
+
+class Always(Matcher):
+    """
+    Match every query; the default for handlers which do not set a matcher.
+    """
+
+    def match(self, qctx: QueryContext) -> bool:
+        return True