diff --git a/A106-xds-unified-matcher-and-cel.md b/A106-xds-unified-matcher-and-cel.md
new file mode 100644
index 000000000..3158f5b5e
--- /dev/null
+++ b/A106-xds-unified-matcher-and-cel.md
@@ -0,0 +1,902 @@
+A106: xDS Unified Matcher and CEL Integration
+======
+
+* Author(s): Sergii Tkachenko (@sergiitk)
+* Approver: Mark Roth (@markdroth)
+* Status: In Review
+* Last updated: 2025-11-20
+* Discussion at: TODO(sergiitk): insert google group thread
+
+## Abstract
+
+We will add support for the xDS [Unified Matcher API] and
+[Common Expression Language] (CEL) within gRPC. This integration will enable
+advanced, flexible matching capabilities for various xDS-managed features, such
+as server-side rate limiting (RLQS, [A77]), Role Based Access Control (RBAC,
+[A41]), and Composite Filter ([A103]).
+
+## Background
+
+[Unified Matcher API] is an adaptable framework that can be used in any xDS
+component that needs matching features. Historically, xDS filters implemented
+its custom mechanisms for performing assertion against response/request
+metadata. The Unified Matcher API was introduced to standardize and unify these
+matching capabilities across various xDS components.
+
+[Common Expression Language](CEL) is an open-source, non-Turing complete
+expression language designed for evaluating expressions quickly and safely. It
+is commonly used in authorization, policy enforcement, and data validation
+scenarios. CEL expressions are evaluated against a set of input variables and
+can perform operations such as comparisons, logical operations, string
+manipulations, and map/list indexing.
+
+CEL is particularly well-suited for xDS because it allows the control plane to
+push user-defined matching logic to gRPC clients, while CEL's non-Turing
+complete nature ensures safe and predictable execution.
+
+The Unified Matcher API is designed to be extensible, allowing for different
+types of inputs and matching logic to be plugged in. CEL integration is achieved
+through this extension mechanism, where CEL expressions can be used as a
+powerful, flexible and safe custom matcher. This allows for complex, dynamic
+request matching based on a wide range of request attributes.
+
+### Related Proposals
+
+* [gRFC A41: xDS RBAC Support][A41]
+* [gRFC A77: xDS Server-Side Rate Limiting][A77] (WIP)
+* [gRFC A103: xDS Composite Filter][A103] (WIP)
+
+[A41]: A41-xds-rbac.md
+[A77]: https://github.com/grpc/proposal/pull/414
+[A103]: https://github.com/grpc/proposal/pull/511
+
+[Unified Matcher API]: https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/matching/matching_api.html
+[Unified Matcher: Filter Integration]: #unified-matcher-filter-integration
+[Unified Matcher: Input Extensions]: #unified-matcher-input-extensions
+[Unified Matcher: Matching Extensions]: #unified-matcher-matching-extensions
+[Unified Matcher: `Matcher`]: #unified-matcher-matcher
+[Unified Matcher: `OnMatch`]: #unified-matcher-onmatch
+[Unified Matcher: `MatcherList`]: #unified-matcher-matcherlist
+[Unified Matcher: `MatcherTree`]: #unified-matcher-matchertree
+[Unified Matcher: `HttpRequestHeaderMatchInput`]: #unified-matcher-httprequestheadermatchinput
+[Unified Matcher: `HttpAttributesCelMatchInput`]: #unified-matcher-httpattributescelmatchinput
+[Unified Matcher: `StringMatcher`]: #unified-matcher-stringmatcher
+[Unified Matcher: `CelMatcher`]: #unified-matcher-celmatcher
+
+[Common Expression Language]: https://cel.dev
+[`cel.expr.CheckedExpr`]: https://github.com/google/cel-spec/blob/master/proto/cel/expr/checked.proto
+
+[CEL Integration]: #cel-integration
+[CEL: `CelExpression`]: #cel-celexpression
+[CEL: Runtime Restrictions]: #cel-runtime-restrictions
+[CEL: Supported Functions]: #cel-supported-functions
+[CEL: Supported Variables]: #cel-supported-variables
+
+[`StringValue`]: https://protobuf.dev/reference/protobuf/google.protobuf/#string-value
+[`TypedExtensionConfig`]: https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/core/v3/extension.proto#L14
+[RE2_wiki]: https://github.com/google/re2/wiki/Syntax
+
+## Proposal
+
+### Unified Matcher
+
+#### Unified Matcher: Core Concepts
+
+The Unified Matcher API revolves around a few key concepts:
+
+1. **Matcher**: A matcher is a rule that evaluates to true or false based on
+ some properties of the input data. Matchers can be simple (e.g., checking if
+ a header has a specific value) or complex (e.g., a boolean combination of
+ other matchers, nested matchers, etc).
+2. **Matcher Action**: If a matcher evaluates to true, an associated action is
+ taken. This action could be anything from selecting a route to applying a
+ filter.
+3. **Matcher Input**: This extracts the data from the Matcher Context and
+ provides it to the matchers evaluate. For example, it may get a specific
+ header from the request provided in the matcher context.
+4. **Matcher Context**: This holds the input data and any other contextual
+ information needed during the matching process. It may include information
+ about the request being processed, the response, connection info, a
+ combination of thereof, additional parameters to mathers that support it,
+ etc.
+
+#### Unified Matcher: Filter Integration
+
+When implementing Unified Matcher API, a filter must define the following for
+each [Unified Matcher: `Matcher`] field in its config:
+
+1. Supported protocol-specific actions.
+2. Supported [Unified Matcher: Input Extensions].
+3. Filter-specific behavior for unsuccessful matches.
+
+Note that the selection of input extensions defines the
+[Unified Matcher: Matching Extensions] that can be used with the filter.
+
+##### Protocol-Specific Actions
+
+Protocol-specific actions are used in
+[`OnMatch.action`][Unified Matcher: `OnMatch`] and may be any protocol-specific
+message packed into [`TypedExtensionConfig`].
+
+The filter implementing the Unified Matcher API must define the set of
+protocol-specific actions it supports. If an action is not supported, gRPC will
+NACK the xDS resource.
+
+Upon a successful match, the matched action will be returned as the result of
+the matcher tree evaluation.
+
+##### Behavior for Unsuccessful Matches
+
+The match is considered unsuccessful:
+
+1. If no match found after evaluating the [Unified Matcher: `Matcher`] AND
+2. `on_no_match` field is unset OR its evaluation is unsuccessful.
+
+The filter may define any behavior for an unsuccessful match, f.e. NACK the xDS
+resource, failing open/closed, etc.
+
+#### Unified Matcher: `Matcher`
+
+While the Unified Matcher API allows for matcher trees of arbitrary depth, gRPC
+will reject any matcher definition with a tree depth greater than `16`, NACKing
+the xDS resource.
+
+Envoy provides two syntactically equivalent Unified Matcher definitions:
+[`envoy.config.common.matcher.v3.Matcher`](https://github.com/envoyproxy/envoy/blob/426cd861187368163b42fce910ab5828f7f0b392/api/envoy/config/common/matcher/v3/matcher.proto)
+and
+[`xds.type.matcher.v3.Matcher`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/matcher.proto),
+which is the preferred version for all new APIs using Unified Matcher. We will
+produce the same form for either one.
+
+We will support the following `Matcher` fields:
+
+- `matcher_type`: One of the following must be present:
+ - [`matcher_list`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/matcher.proto#L134)
+ ([Unified Matcher: `MatcherList`]).
+ - [`matcher_tree`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/matcher.proto#L137)
+ ([Unified Matcher: `MatcherTree`]).
+- [`on_no_match`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/matcher.proto#L143):
+ ([Unified Matcher: `OnMatch`]): Specifies the action executed if no match is
+ found in the `matcher_list` or `matcher_tree`. If not set, refer to filter's
+ [unsuccessful match behavior][Unified Matcher: Filter Integration].
+
+#### Unified Matcher: `OnMatch`
+
+We will support the following fields in the
+[`xds.type.matcher.v3.Matcher.OnMatch`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/matcher.proto#L24)
+message:
+
+- `on_match`: One of the following must be present:
+ - [`matcher`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/matcher.proto#L33)
+ ([Unified Matcher: `Matcher`]): A nested matcher that allows for
+ building more complex, tree-like matching logic.
+ - [`action`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/matcher.proto#L36)
+ ([`TypedExtensionConfig`]): If set, must contain one of the
+ protocol-specific actions
+ [supported by the filter][Unified Matcher: Filter Integration].
+- [`keep_matching`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/matcher.proto#L45)
+ (bool): If this field is set in a context in which it's not supported, the
+ xDS resource will be NACKed.
+
+#### Unified Matcher: `MatcherList`
+
+[`Matcher.MatcherList.Predicate`]: https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/matcher.proto#L53
+[`Matcher.MatcherList.Predicate.PredicateList`]: https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/matcher.proto#L73
+
+We will support the following fields in the
+[`xds.type.matcher.v3.Matcher.MatcherList`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/matcher.proto#L51)
+message:
+
+- [`matchers`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/matcher.proto#L104)
+ (repeated
+ [`Matcher.MatcherList.FieldMatcher`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/matcher.proto#L95)):
+ Must contain at least 1 item.
+ - [`predicate`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/matcher.proto#L97)
+ ([`Matcher.MatcherList.Predicate`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/matcher.proto#L53)):
+ Must be present.
+ - `match_type`: One of the following must be present:
+ - [`single_predicate`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/matcher.proto#L81)
+ ([`Matcher.MatcherList.Predicate.SinglePredicate`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/matcher.proto#L55)):
+ If set, the return type of the `input` must match the input type
+ of the `matcher`.
+ - [`input`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/matcher.proto#L58)
+ ([`TypedExtensionConfig`]): Must be present and contain one
+ of the input extensions
+ [supported by the filter][Unified Matcher: Filter Integration].
+ Must have return type compatible with the `matcher`.
+ - `matcher`: One of the following must be present:
+ - [`value_match`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/matcher.proto#L64)
+ ([Unified Matcher: `StringMatcher`]): Only compatible
+ with `input` that returns a string.
+ - [`custom_match`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/matcher.proto#L68)
+ ([`TypedExtensionConfig`]): Must contain one of the
+ matching extensions
+ [supported by the filter][Unified Matcher: Filter Integration].
+ Must be compatible with the return type of the `input`.
+ - [`or_matcher`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/matcher.proto#L84)
+ ([`Matcher.MatcherList.Predicate.PredicateList`]):
+ - [`predicate`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/matcher.proto#L74)
+ (repeated [`Matcher.MatcherList.Predicate`]): Must contain
+ at least 2 items. Returns true if any of them are true.
+ - [`and_matcher`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/matcher.proto#L87)
+ ([`Matcher.MatcherList.Predicate.PredicateList`]):
+ - [`predicate`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/matcher.proto#L74)
+ (repeated [`Matcher.MatcherList.Predicate`]): Must contain
+ at least 2 items. Returns true if all of them are true.
+ - [`not_matcher`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/matcher.proto#L90)
+ ([`Matcher.MatcherList.Predicate`]): Returns the inverted result
+ of predicate evaluation.
+ - [`on_match`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/matcher.proto#L100)
+ ([Unified Matcher: `OnMatch`]): Must be present.
+
+#### Unified Matcher: `MatcherTree`
+
+[`Matcher.MatcherTree.MatchMap`]: https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/matcher.proto#L109
+
+We will support the following fields in the
+[`xds.type.matcher.v3.Matcher.MatcherTree`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/matcher.proto#L107)
+message:
+
+- [`input`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/matcher.proto#L114)
+ ([`TypedExtensionConfig`]): Must be present and contain one of the input
+ extensions
+ [supported by the filter][Unified Matcher: Filter Integration]. Must have
+ return type compatible with each matcher specified in the tree.
+- `tree_type`: One of the following must be present:
+ - [`exact_match_map`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/matcher.proto#L122)
+ ([`Matcher.MatcherTree.MatchMap`]): Only compatible with `input` that
+ returns a `string`.
+ - [`map`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/matcher.proto#L110)
+ A map from a `string` to [Unified Matcher: `OnMatch`]. Must contain
+ at least 1 pair.
+ - [`prefix_match_map`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/matcher.proto#L125)
+ ([`Matcher.MatcherTree.MatchMap`]): Only compatible with `input` that
+ returns a `string`.
+ - [`map`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/matcher.proto#L110)
+ A map from a `string` to [Unified Matcher: `OnMatch`]. Must contain
+ at least 1 pair.
+
+The following are not supported by gRPC in the initial implementation and will
+result in xDS resource NACK:
+
+- `custom_match`
+
+#### Unified Matcher: Input Extensions
+
+In this iteration, the following Unified Mather extensions will be supported:
+
+1. [Unified Matcher: `HttpRequestHeaderMatchInput`]
+2. [Unified Matcher: `HttpAttributesCelMatchInput`]
+
+##### Unified Matcher: `HttpRequestHeaderMatchInput`
+
+Returns a `string` containing the value of the header with name specified in
+`header_name`.
+
+We will support the following fields in the
+[`envoy.type.matcher.v3.HttpRequestHeaderMatchInput`](https://github.com/envoyproxy/envoy/blob/426cd861187368163b42fce910ab5828f7f0b392/api/envoy/type/matcher/v3/http_inputs.proto#L22)
+message:
+
+- [`header_name`](https://github.com/envoyproxy/envoy/blob/426cd861187368163b42fce910ab5828f7f0b392/api/envoy/type/matcher/v3/http_inputs.proto#L24):
+ Must be present. Value length must be in the range `[1, 16384)`. Must be a
+ valid HTTP/2 header name.
+
+##### Unified Matcher: `HttpAttributesCelMatchInput`
+
+Returns a language-specific interface that allows to access request RPC metadata
+as defined in [CEL: Supported Variables].
+
+We will support the following fields in the
+[`xds.type.matcher.v3.HttpAttributesCelMatchInput`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/http_inputs.proto#L22)
+message:
+
+- no fields.
+
+#### Unified Matcher: Matching Extensions
+
+In this iteration, the following Unified Mather extensions will be supported:
+
+1. [Unified Matcher: `StringMatcher`](standard matcher)
+2. [Unified Matcher: `CelMatcher`]
+
+##### Unified Matcher: `StringMatcher`
+
+Compatible with [Unified Matcher: Input Extensions] that return a `string`.
+
+We will support the following fields in the
+[`xds.type.matcher.v3.StringMatcher`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/string.proto#L19)
+message:
+
+- `match_pattern`: One of the following must be present:
+ - [`exact`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/string.proto#L28):
+ The input string must match exactly. An empty string is a valid value.
+ - [`prefix`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/string.proto#L36):
+ The input string must have this prefix. Must be non-empty.
+ - [`suffix`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/string.proto#L44):
+ The input string must have this suffix. Must be non-empty.
+ - [`safe_regex`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/string.proto#L47):
+ ([`RegexMatcher`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/regex.proto#L15))
+ The input string must match the regular expression.
+ - [`google_re2`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/regex.proto#L40):
+ Effectively ignored, [Google's RE2][RE2_wiki] is the only supported
+ implementation.
+ - [`regex`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/regex.proto#L45)
+ Must be non-empty.
+ - [`contains`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/string.proto#L55):
+ The input string must contain this substring. Must be non-empty.
+- [`ignore_case`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/string.proto#L65):
+ If `true`, the matching is case-insensitive. Does not apply to the
+ `safe_regex` match type.
+
+The following are not supported by gRPC and will result in xDS resource NACK:
+
+- `custom`
+
+The following fields are ignored:
+
+- `RegexMatcher.google_re2`
+
+##### Unified Matcher: `CelMatcher`
+
+Compatible with [Unified Matcher: `HttpAttributesCelMatchInput`].
+
+Performs a match by evaluating a [Common Expression Language] (CEL) expression.
+See [CEL Integration] for details.
+
+We will support the following fields in the
+[`xds.type.matcher.v3.CelMatcher`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/cel.proto#L30)
+message:
+
+- [`expr_match`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/cel.proto#L32)
+ ([`xds.type.v3.CelExpression`][CEL: `CelExpression`]): Must be present.
+ This message will be converted into a native CEL Abstract Syntax Tree (AST)
+ using the language-specific CEL library. The AST's output (return) type must
+ be boolean. The resulting CEL program must also be validated to conform to
+ [CEL: Runtime Restrictions] and only contain [CEL: Supported Variables]. If
+ the conversion or the validation step fail, gRPC will NACK the xDS resource.
+- [`description`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/matcher/v3/cel.proto#L36):
+ An optional string. May be ignored or used for testing/debugging.
+
+#### Unified Matcher: Evaluation Flow
+
+**Goal:** To produce a list of matching `Action`s.\
+**Matcher Context:** The input to the Matcher evaluation tree. Provided at
+runtime by the filter, contains the input data. May contain any other contextual
+information relevant to the filter. Implementation note: when setting an input,
+consider memory footprint. For example, instead of resolving all headers in
+advance, provide them in a lazy-loading wrapper.
+**Matching Process:** Starting with a top-level `Matcher`, there will be one
+of the following matchers types:
+
+- **[List Matcher][Unified Matcher: `MatcherList`]:** This is like a series of
+ "if-elif-elif-else" statements. It goes through a list of rules:
+ - Each rule has a **Condition** (`Predicate`) and a **Result** (`OnMatch`).
+ - **Condition Checking:** To check a typical condition:
+ 1. Using the `input` extension, extract a specific piece of data from
+ the **Matcher Context** (e.g., the value of requests's `:host`
+ header).
+ 2. Using the `matcher` extension, compare the extracted data against
+ the matcher's criteria (e.g., "is it equal to `'example.com'`?",
+ "does it start with `'api.'`?").
+ - Conditions can be combined using AND, OR, NOT, or nested via `OnMatch`.
+ - **First Match Wins:** The *first* rule whose **Condition** is true has
+ its **Result** executed.
+
+- **[Exact Map Matcher][Unified Matcher: `MatcherTree`]:** This is like a
+ switch statement or dictionary lookup.
+ - Using the `input` extension, it extracts a specific string value from
+ the **Matcher Context**.
+ - It looks this the key for this exact string the a predefined map.
+ - If found, it executes the corresponding **Result**.
+
+- **[Prefix Map Matcher][Unified Matcher: `MatcherTree`]:** Similar to the Map
+ Matcher, but uses prefix matching (a Trie data structure).
+ - Using the `input` extension, it extracts a specific string value from
+ the **Matcher Context**.
+ - It finds all entries in the map whose keys are prefixes of the input
+ string.
+ - The **Result** is chosen based the the key with the *longest* matching
+ prefix.
+ - Undefined behavior: If there are multiple prefixes of the same greatest
+ length, their **Result**s are all processed in order of Trie traversal.
+ There's no other tie-breaking rule like alphabetical order among the
+ tied keys.
+
+**[Result][Unified Matcher: `OnMatch`]:** When a match occurs, the `OnMatch`
+dictates the outcome:
+
+- It can contain an `Action` to be added to the results.
+- It can contain a nested `Matcher`, triggering a further round of matching.
+ - The tree is validated to not contain matchers with the tree depth
+ greater than `16`. If this three depth is reached at runtime,
+ the tree evaluation is terminated, and considered an
+ [unsuccessful match][Unified Matcher: Filter Integration].
+- `keep_matching` determines whether a successful match within an `OnMatch`
+ should be considered "terminal" for the current `Matcher` being evaluated.
+ It essentially answers the question: "After processing this `OnMatch`,
+ should the current matcher stop looking for more matches, or continue?"
+ - If `keep_matching` is `false` (the usual case), finding this `OnMatch`
+ is terminal. The `Action` is added (or the nested `Matcher` is
+ evaluated), and the current matcher stops searching.
+ - If `keep_matching` is `true`, the `Action` is added (or nested
+ `Matcher` evaluated), but the current matcher *continues* to look for
+ more matches. The overall process is not considered complete until an
+ `OnMatch` with `keep_matching` set to `false` is encountered.
+
+**Default/No Match:** Any `Matcher` can have a default `OnMatch` to use if none
+of its primary conditions or map lookups succeed. See details in
+[Unified Matcher: `Matcher`] and [Unified Matcher: Filter Integration].\
+**The Output:** A list of `Action`s accumulated from all triggered `OnMatch`
+results. Generally, only a single `Action` will be returned, unless
+`keep_matching` is enabled and multiple matches found.
+
+#### Unified Matcher: Evaluation Examples
+
+For simplicity:
+
+- `TypedExtensionConfig` fields: The type is captured in a comment, and the
+ value directly contains the unpacked message content.
+- `on_match` action: Represented by a string, for example,
+ `"on_match": { "action": "route_to_cluster_A" }`.
+- The first example will include `input` to demonstrate the data flow.
+- Other examples will skip the input and simply indicates the result of
+ evaluation in `custom_match` field, for example, `{ "custom_match": true }`.
+
+For even more examples, refer to
+[Envoy's Unified Matcher API Documentation][Unified Matcher API] (note that
+these examples might be Envoy-specific).
+
+##### Example 1: Simple Linear Match
+
+This example shows a basic matcher list. It routes requests based on the value
+of a single header, the first matching predicate wins.
+
+**Configuration:**
+
+```json5
+{
+ "matcher_list": {
+ "matchers": [
+ {
+ "predicate": {
+ "single_predicate": {
+ // envoy.type.matcher.v3.HttpRequestHeaderMatchInput
+ "input": { "header_name": "x-user-segment" },
+ "value_match": { "exact": "premium" }
+ }
+ },
+ "on_match": { "action": "route_to_premium_cluster" }
+ },
+ {
+ "predicate": {
+ "single_predicate": {
+ // envoy.type.matcher.v3.HttpRequestHeaderMatchInput
+ "input": { "header_name": "x-user-segment" },
+ "value_match": { "prefix": "standard-" }
+ }
+ },
+ "on_match": { "action": "route_to_standard_cluster" }
+ }
+ ]
+ },
+ "on_no_match": { "action": "route_to_default_cluster" }
+}
+```
+
+**Request Input 1:**
+
+- Headers: `{ "x-user-segment": "standard-user-1" }`
+
+**Evaluation (detailed):**
+
+1. The `matcher_list` evaluates its matchers in order.
+2. The first matcher is evaluated.
+ - The `input` executes `HttpRequestHeaderMatchInput` extension:
+ - The extension logic extracts the value of the `x-user-segment`
+ header from the Matcher Context.
+ - The `input` returns `standard-user-1`.
+ - The `StringMatcher` is evaluated (standard matcher):
+ - The input is a string `standard-user-1`, which is the correct input
+ type for this matcher.
+ - The `StringMatcher` checks if the value `standard-user-1` has
+ the exact value `premium`.
+ - The result of matcher evaluation is `false`
+ - The predicate is `false`, matching continues.
+3. The second matcher is evaluated:
+ - The `input` executes `HttpRequestHeaderMatchInput` extension:
+ - The extension logic extracts the value of the `x-user-segment`
+ header from the Matcher Context.
+ - The `input` returns `standard-user-1`.
+ - The `StringMatcher` is evaluated (standard matcher):
+ - The input is a string `standard-user-1`, which is the correct input
+ type for this matcher.
+ - The `StringMatcher` checks if the value `standard-user-1` has the
+ prefix `standard-`.
+ - The result of matcher evaluation is `true`
+ - The predicate is `true`, its `on_match` is evaluated.
+ - The action `route_to_standard_cluster` is chosen.
+ - The `matcher_list` stops processing further matchers because
+ `keep_matching` is not set.
+
+**Result 1:** `["route_to_standard_cluster"]`.
+
+**Request Input 2:**
+
+- Headers: `{ "x-user-segment": "guest" }`
+
+**Evaluation (simplified):**
+
+1. The `matcher_list` evaluates its matchers in order.
+2. The first matcher for `premium` is `false`.
+3. The second matcher for `standard-` prefix is `false`.
+4. No matchers in the list evaluated to `true`, therefore the `on_no_match` is
+ evaluated:
+ - The action `route_to_default_cluster` is chosen.
+
+**Result 2:** ["route_to_default_cluster"]
+
+##### Example 2: Keep Matching
+
+This example demonstrates the effect of `keep_matching: true`. Actions are
+accumulated until a matcher with `keep_matching: false` (the default) is found.
+
+**Configuration:**
+
+```json5
+{
+ "matcher_list": {
+ "matchers": [
+ // Matcher 1
+ {
+ "predicate": { "single_predicate": { "custom_match": true } },
+ "on_match": { "action": "action_1", "keep_matching": true }
+ },
+ // Matcher 2
+ {
+ "predicate": { "single_predicate": { "custom_match": false } },
+ "on_match": { "action": "action_2" }
+ },
+ // Matcher 3
+ {
+ "predicate": { "single_predicate": { "custom_match": true } },
+ "on_match": { "action": "action_3" }
+ },
+ // Matcher 4
+ {
+ "predicate": { "single_predicate": { "custom_match": false } },
+ "on_match": { "action": "action_4" }
+ }
+ ]
+ }
+}
+```
+
+**Evaluation:**
+
+1. Matcher 1 evaluates to `true`.
+ - `action_1` is added to the result list.
+ - Matching continues because `keep_matching: true`.
+2. Matcher 2 evaluates to `false`.
+3. Matcher 3 evaluates to `true`.
+ - `action_3` is added to the result list.
+ - Matching stops because `keep_matching` is false by default.
+4. Matcher 4 is not evaluated.
+
+**Result:** `["action_1", "action_3"]`
+
+##### Example 3: Nested Matcher
+
+This example shows a matcher whose action is another matcher.
+
+**Configuration:**
+
+```json5
+{
+ "matcher_list": {
+ "matchers": [
+ {
+ "predicate": { "single_predicate": { "custom_match": true } },
+ "on_match": {
+ // Nested matcher with matcher_list.
+ "matcher": {
+ "matcher_list": {
+ "matchers": [
+ {
+ "predicate": { "single_predicate": { "custom_match": false } },
+ "on_match": { "action": "inner_matcher_1" }
+ },
+ {
+ "predicate": { "single_predicate": { "custom_match": true } },
+ "on_match": { "action": "inner_matcher_2" }
+ }
+ ]
+ }
+ }
+ // Nested matcher end.
+ }
+ }
+ ]
+ }
+}
+```
+
+**Evaluation:**
+
+1. The outer `matcher_list` evaluates its first (and only) matcher:
+ - The predicate of the outer matcher evaluates to `true`.
+ - The `on_match` of the outer matcher contains a nested matcher.
+ - The three depth is not greater than 16, the nested matcher is evaluated:
+ 1. The inner `matcher_list` evaluates first matcher to `false`.
+ 2. The inner `matcher_list` evaluates its second matcher to `true`:
+ - The predicate is `true`, its `on_match` is evaluated.
+ - The action `inner_match_2` is added to the result list.
+ - Evaluation stops because `keep_matching` is not set.
+
+**Result 1:** `["inner_matcher_2"]`
+
+##### Example 4: Prefix Map Matcher
+
+This shows a prefix map, where the longest prefix match wins.
+
+**Configuration:**
+
+```json5
+{
+ "matcher_prefix_map": {
+ // envoy.type.matcher.v3.HttpRequestHeaderMatchInput
+ "input": { "header_name": "x-user-segment" },
+ "map": {
+ "grpc": { "action": "shorter_prefix" },
+ "grpc.channelz": { "action": "longer_prefix" }
+ }
+ }
+}
+```
+
+**Request Input:**
+
+- Path: `grpc.channelz.v1.Channelz/GetTopChannels`
+
+**Evaluation:**
+
+1. The input path `grpc.channelz.v1.Channelz/GetTopChannels` is checked against
+ the map keys.
+2. It matches both `grpc` and `grpc.channelz`.
+3. The longest matching prefix is `grpc.channelz`.
+ - The action `longer_prefix` is chosen.
+
+**Result 1:** `["longer_prefix"]`
+
+---
+
+### CEL Integration
+
+We will support request metadata matching via CEL expressions.
+
+CEL evaluation environment is a set of available variables and extension
+functions in a CEL program. We will match
+[Envoy CEL environment](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes)
+and CEL interpreter configuration.
+
+#### CEL: `CelExpression`
+
+[`xds.type.v3.CelExpression`]: https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/v3/cel.proto#L26
+
+CEL expressions will be provided by the xDS Control Plane in the
+[`xds.type.v3.CelExpression`] message, which allows to specify CEL Abstract
+Syntax Tree (AST) in different forms (e.g., `googleapis` or canonical, and each
+may be either parsed or checked). We will only support one form: type-checked
+Canonical CEL, specifically the [`cel.expr.CheckedExpr`] message.
+
+We will support the following fields in the [`xds.type.v3.CelExpression`]
+message:
+
+- [`cel_expr_checked`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/v3/cel.proto#L49)
+ ([`cel.expr.CheckedExpr`]): Must be present.
+
+The following fields will be ignored by gRPC:
+
+- `parsed_expr` - deprecated, only Canonical CEL is supported.
+- `checked_expr` - deprecated, only Canonical CEL is supported.
+- `cel_expr_parsed` - only Checked CEL expressions are supported.
+
+The following are not supported by gRPC in the initial implementation and will
+result in xDS resource NACK:
+
+- `cel_expr_string`
+
+#### CEL: `CelExtractString`
+
+`CelExtractString` is a small tool that allows to extract a string from
+[CEL: Supported Variables] using a CEL expression. The expression must evaluate
+to a `string`.
+
+We will support the following fields in the
+[`xds.type.v3.CelExtractString`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/v3/cel.proto#L69)
+message:
+
+- [`expr_extract`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/v3/cel.proto#L72)
+ ([`xds.type.v3.CelExpression`][CEL: `CelExpression`]): Must be present.
+ This message will be converted into a native CEL Abstract Syntax Tree (AST)
+ using the language-specific CEL library. The AST's output (return) type must
+ be a `string`. It may only contain [CEL: Supported Functions] and
+ [CEL: Supported Variables]. The resulting CEL program must also be validated
+ to conform to [CEL: Runtime Restrictions]. If the conversion or the
+ validation step fail, gRPC will NACK the xDS resource.
+- [`default_value`](https://github.com/cncf/xds/blob/2ac532fd44436293585084f8d94c6bdb17835af0/xds/type/v3/cel.proto#L76):
+ ([`StringValue`]) Optional. If set, and the CEL expression evaluates to an
+ error or a non-string type, this default value will be returned instead.
+
+#### CEL: Runtime Restrictions
+
+Certain CEL features can lead to superlinear time complexity or memory
+exhaustion. To ensure consistent behavior with Envoy and maintain security, gRPC
+will configure the CEL runtime
+[similar to Envoy](https://github.com/envoyproxy/envoy/blob/c57801c2afbe26dd6fad7f5ce764f267d07fbd04/source/extensions/filters/common/expr/evaluator.cc#L17-L23):
+
+```cpp
+// Disables comprehension expressions, e.g. exists(), all().
+options.enable_comprehension = false;
+
+// Limits the maximum program size for RE2 regex to 100.
+options.regex_max_program_size = 100;
+
+// Disables string() overloads.
+options.enable_string_conversion = false;
+
+// Disables string concatenation overload.
+options.enable_string_concat = false;
+
+// Disables list concatenation overload.
+options.enable_list_concat = false;
+```
+
+#### CEL: Supported Functions
+
+Similar to Envoy, we will support
+[standard CEL functions](https://github.com/google/cel-spec/blob/c629b2be086ed6b4c44ef4975e56945f66560677/doc/langdef.md#standard-definitions)
+except comprehension-style macros.
+
+| CEL Method | Description |
+|----------------------------------------------------|-----------------------------------------------------------------------------------------------|
+| `size(x)` | Returns the length of a container x (string, bytes, list, map). |
+| `x.matches(y)` | Returns true if the string x is partially matched by the specified [RE2][RE2_wiki] pattern y. |
+| `x.contains(y)` | Returns true if the string x contains the substring y. |
+| `x.startsWith(y)` | Returns true if the string x begins with the substring y. |
+| `x.endsWith(y)` | Returns true if the string x ends with the substring y. |
+| `timestamp(x)`, `timestamp.get*(x)`, `duration` | Date/time functions. |
+| `in`, `[]` | Map/list indexing. |
+| `has(m.x)` | (macro) Returns true if the map `m` has the string `"x"` as a key. |
+| `int`, `uint`, `double`, `string`, `bytes`, `bool` | Conversions and identities. |
+| `==`, `!=`, `>`, `<`, `<=`, `>=` | Comparisons. |
+| `or`, `&&`, `+`, `-`, `/`, `*`, `%`, `!` | Basic functions. |
+
+#### CEL: Supported Variables
+
+In the initial implementation only the `request` variable is supported in CEL
+expressions. We will adapt
+[Envoy's Request Attributes](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes#request-attributes)
+for gRPC.
+
+| Attribute | Type | gRPC source | Envoy Description |
+|---------------------|-----------------------|------------------------------|-------------------------------------------------------------|
+| `request.path` | `string` | Full method name | The path portion of the URL. |
+| `request.url_path` | `string` | Same as `request.path` | The path portion of the URL without the query string. |
+| `request.host` | `string` | Authority | The host portion of the URL. |
+| `request.scheme` | `string` | Not set | The scheme portion of the URL. |
+| `request.method` | `string` | `POST`1 | Request method. |
+| `request.headers` | `map` | `metadata`2 | All request headers indexed by the lower-cased header name. |
+| `request.referer` | `string` | `metadata["referer"]` | Referer request header. |
+| `request.useragent` | `string` | `metadata["user-agent"]` | User agent request header. |
+| `request.time` | `timestamp` | Not set | Time of the first byte received. |
+| `request.id` | `string` | `metadata["x-request-id"]` | Request ID corresponding to `x-request-id` header value |
+| `request.protocol` | `string` | Not set | Request protocol. |
+| `request.query` | `string` | `""` | The query portion of the URL. |
+
+##### Footnotes
+
+**1 `request.method`** \
+Hard-coded to `"POST"` if unavailable and a code audit confirms the server
+denies requests for all other method types.
+
+**2 `request.headers`** \
+As defined in [A41], "header" field.
+
+##### CEL: Variable Implementation Details
+
+For performance reasons, CEL variables should be resolved on demand. CEL Runtime
+provides the different variable resolving approaches based on the language:
+
+- CPP:
+ [`BaseActivation::FindValue()`](https://github.com/google/cel-cpp/blob/9310c4910e598362695930f0e11b7f278f714755/eval/public/base_activation.h#L35)
+- Go:
+ [`Activation.ResolveName(string)`](https://github.com/google/cel-go/blob/3f12ecad39e2eb662bcd82b6391cfd0cb4cb1c5e/interpreter/activation.go#L30)
+- Java:
+ [`CelVariableResolver`](https://javadoc.io/doc/dev.cel/runtime/0.6.0/dev/cel/runtime/CelVariableResolver.html)
+
+#### CEL: Unified Matcher Example
+
+CEL will be integrated into the Unified Matcher API like so:
+
+```textproto
+matcher_list {
+ matchers {
+ predicate {
+ single_predicate {
+ input {
+ typed_config: {
+ [type.googleapis.com/xds.type.matcher.v3.HttpAttributesCelMatchInput] {}
+ }
+ }
+ custom_match: {
+ typed_config: {
+ [type.googleapis.com/xds.type.matcher.v3.CelMatcher] {
+ expr_match: {
+ cel_expr_checked: {
+ # Checked CEL AST here.
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ on_match: {
+ # Action on successful match.
+ }
+ }
+}
+```
+
+### Temporary Environment Variable Protection
+
+The Unified Matcher API feature will not be guarded by a dedicated environment
+variable. The environment variable protection will be handled by the features
+that depend on it (e.g., `GRPC_EXPERIMENTAL_XDS_ENABLE_RLQS` for RLQS).
+
+## Rationale
+
+The chosen approach, integrating the xDS Unified Matcher API with CEL, was
+selected over several alternatives due to its advantages in consistency,
+maintainability, safety, and alignment with the broader xDS ecosystem.
+
+### Considered Alternatives
+
+The main alternative is for each xDS feature (e.g., RLQS, RBAC) to define its
+own custom matching logic. This is how older xDS features were designed, but it
+leads to significant drawbacks:
+
+- Duplication and Inconsistency: It forces repeated implementation of
+ common matching primitives (header, path, etc.) across different filters and
+ gRPC language implementations, leading to code bloat and subtle behavioral
+ differences.
+- High Maintenance Cost: Bug fixes and new features must be implemented in
+ multiple places.
+
+The Unified Matcher API provides a single, consistent, and reusable framework
+that is implemented once and shared by all features. This improves
+maintainability, provides a uniform configuration experience for users, and
+aligns gRPC with Envoy, creating a more cohesive xDS ecosystem.
+
+### Disadvantages and Trade-offs
+
+- **Increased Complexity**: The system is powerful but also complex. The
+ matcher API involves nested structures, different evaluation flows
+ (`MatcherList` vs. `MatcherTree`), and nuanced behaviors like
+ `keep_matching`. Debugging this can be more challenging than simpler
+ matching schemes.
+- **Restricted CEL Functionality**: To ensure safety and performance, the
+ implementation explicitly disables certain CEL features, such as
+ comprehensions (`exists()`, `all()`). This is a direct trade-off of power
+ for safety, meaning not all standard CEL capabilities are available.
+- **Performance Overhead**: Evaluating CEL expressions for every request
+ introduces computational overhead. While designed to be fast, it will be
+ slower than simple, hard-coded logic or basic string comparisons.
+
+## Implementation
+
+Will be implemented in C-core, Java, Go, and Node as part of either RLQS
+([A77]) or Composite Filter ([A103]), whichever happens to be implemented first
+in any given language. Role Based Access Control (RBAC, [A41]) currently does
+not support the Unified Matcher API in gRPC, though it is supported by Envoy,
+but it may be added in the future.