@@ -91,32 +91,24 @@ type Route struct {
9191 // CanonicalChainKey is the key of the policy chain this route's requests use
9292 // when the operation is determined by the route itself. It equals the route key
9393 // for every kind shipping today, but it is emitted as its own explicit field
94- // rather than left implicit, because that is what lets an identity route be
95- // pointed at a *composed* operation key without a wire change — which is exactly
96- // what an A2A HTTP+JSON operation route does, one route per operation path.
97- // Empty means "same as the route key".
94+ // rather than left implicit, because that is what lets a directly-resolved route be
95+ // pointed at a *composed* operation key without a wire change. Empty means "same as
96+ // the route key".
9897 //
99- // A resolver-bearing route must leave this empty: it has no chain of its own, its
100- // chain being composed per request from the operation the resolver identifies.
98+ // A route naming a protocol resolver must leave this empty. Its key is derived from
99+ // its own ResolverConfig by the resolver that owns the protocol, so a key here would
100+ // be a second copy of the same fact with nothing to arbitrate between them — see
101+ // ValidateResolution, which rejects the combination.
101102 CanonicalChainKey string
102103
103104 // ResolverName overrides RuntimeDeployConfig.PolicyChainResolver for this one
104- // route. It exists because a multiplexed API has both kinds of route at once: an
105- // A2A deployment serves identity HTTP+JSON routes and a body-resolved JSON-RPC
106- // route side by side. Empty inherits the RDC-level default.
105+ // route. It exists because one API can hold both shapes at once: routes whose
106+ // operation is only knowable from the request name a protocol resolver, while the
107+ // routes that are not operations at all — a well-known metadata document, a CORS
108+ // preflight — stay directly resolved beside them. Empty inherits the RDC-level
109+ // default.
107110 ResolverName string
108111
109- // ResponseKind is how this route's operation delivers its response: "unary",
110- // "streaming", or empty for "the policy engine derives it as it always has".
111- //
112- // It exists because buffering a streaming response is not a slow path but a broken
113- // one — the caller gets nothing until the upstream closes, which for a long-running
114- // agent task may be never — and the policy chain alone cannot tell the engine which
115- // it is. Set it on a route whose operation is known at deploy time (one A2A HTTP+JSON
116- // path per operation). On a multiplexed route leave it empty: both kinds of operation
117- // arrive on the same route, so only the resolver can say which this request is.
118- ResponseKind string
119-
120112 // ResolverConfig is opaque, resolver-specific per-route configuration. The policy
121113 // engine passes it to the resolver's Prepare hook once at xDS ingest, so a
122114 // resolver that must compile a schema or build an index does it there rather than
@@ -197,18 +189,6 @@ type ConfigTransformer interface {
197189 Transform (cfg * StoredConfig ) (* RuntimeDeployConfig , error )
198190}
199191
200- // Response kinds a route may declare. They must match the policy engine's
201- // resolver.ResponseKind values; the two are separate declarations because the controller
202- // and the runtime are separate modules agreeing on a wire value, and a mismatch here is
203- // caught loudly — the engine ignores a kind it does not recognise and logs it, and
204- // ValidateResolution refuses to emit one in the first place.
205- const (
206- ResponseKindUnary = "unary"
207- ResponseKindStreaming = "streaming"
208- )
209-
210- var validResponseKinds = map [string ]bool {"" : true , ResponseKindUnary : true , ResponseKindStreaming : true }
211-
212192// RouteKeyResolverName is the resolver name meaning "the route determines the
213193// operation" — the identity case. It must match the policy engine's
214194// resolver.RouteKeyResolverName; the two are separate constants because the
@@ -234,10 +214,14 @@ func (rdc *RuntimeDeployConfig) EffectiveCanonicalChainKey(routeKey string, rout
234214 return routeKey
235215}
236216
237- // isIdentityResolver reports whether a resolver name means "the route determines the
238- // operation". An empty name does, because that is what every RDC looked like before
239- // resolvers existed.
240- func isIdentityResolver (name string ) bool {
217+ // IsDirectlyResolved reports whether a resolver name means "the route itself determines
218+ // the chain key", so the route carries that key rather than deriving one per request. An
219+ // empty name does, because that is what every RDC looked like before resolvers existed.
220+ //
221+ // Exported because the snapshot translator decides from it whether to put
222+ // canonical_chain_key on the wire at all, and that decision has to agree with the
223+ // validation rule below — two predicates could disagree.
224+ func IsDirectlyResolved (name string ) bool {
241225 return name == "" || name == RouteKeyResolverName
242226}
243227
@@ -255,8 +239,8 @@ func isIdentityResolver(name string) bool {
255239// from "the map points at a missing chain" to "a key the engine will compose has no
256240// chain", so the checks moved with it:
257241//
258- // - an identity route's canonical key must name a chain (this covers a redirected
259- // HTTP+JSON operation route , whose composed key must resolve like any other);
242+ // - a directly-resolved route's canonical key must name a chain (including one pointed
243+ // at a composed operation key , whose key must resolve like any other);
260244// - a resolver-bearing route must not carry a canonical key, and must have at least
261245// one operation chain in its own partition — otherwise no request to it can ever
262246// resolve;
@@ -300,21 +284,16 @@ func (rdc *RuntimeDeployConfig) ValidateResolution() error {
300284 return fmt .Errorf ("route %q: nil route" , routeKey )
301285 }
302286
303- if ! validResponseKinds [route .ResponseKind ] {
304- return fmt .Errorf ("route %q: unknown response kind %q (expected %q, %q, or empty)" ,
305- routeKey , route .ResponseKind , ResponseKindUnary , ResponseKindStreaming )
306- }
307-
308287 resolverName := rdc .EffectiveResolverName (route )
309288
310- if isIdentityResolver (resolverName ) {
289+ if IsDirectlyResolved (resolverName ) {
311290 canonical := rdc .EffectiveCanonicalChainKey (routeKey , route )
312291 if _ , ok := rdc .PolicyChains [canonical ]; ! ok {
313292 return fmt .Errorf ("route %q: canonical chain key %q names no policy chain" , routeKey , canonical )
314293 }
315- // Existing is not enough. A redirected identity route (an A2A HTTP+JSON
316- // operation route) points at a composed key, and a chain composed for another
317- // routing partition is a perfectly valid chain that belongs to someone else:
294+ // Existing is not enough. A directly-resolved route may be pointed at a
295+ // composed operation key, and a chain composed for another routing partition
296+ // is a perfectly valid chain that belongs to someone else:
318297 // a production route pointed at a sandbox operation chain would run the
319298 // sandbox's authentication, authorization and rate limits. Existence checks
320299 // catch a missing chain; only this catches the wrong one.
0 commit comments