a2aproject / a2aproject/a2a-rs

a2a-client: the selected AgentInterface's tenant is never echoed on requests (A2A §8.3.2)

Abierto
#199 0 comentarios 0 reacciones 0 asignados Ver en GitHub
bug
Lenguaje dominante
Rust
Estrellas
75
Forks
19
Merge medio
11 h 27 min
PR fusionados (30 d)
21

Descripción

## Summary

A2A §8.3.2 rule 4 requires a client to

> set the `tenant` field in every request message to exactly the value declared in the selected `AgentInterface` entry (omit the field if `tenant` is not set in that entry)

`a2a-client` has no mechanism for this. Request types carry `tenant: Option`, but nothing populates it from the Agent Card, so a multi-tenant A2A deployment cannot be addressed through this SDK.

#176 added `A2AClientFactory::create_from_card_with_interface`, which returns the selected `AgentInterface` so a **caller** can read `.tenant` and fill it in themselves. That is what `a2acli` does. It made compliance *possible*; it did not make the library compliant. Every other consumer of `a2a-client` still cannot comply, and each one has to remember to do it on all eleven request methods.

## Two things to get right

**The declared tenant must win.** The rule says "exactly the value declared", so when the selected interface declares a tenant, a caller-supplied one must not displace it. A fill-only-if-unset policy leaves the hole open: a caller that sets a different tenant still sends the wrong one, and the SDK reports no error. The declared value is the routing identifier *for that interface* — there is no legitimate reading in which a caller overrides it.

**An absent declaration must stay absent.** When the entry declares no tenant the field is omitted, not defaulted. An empty string is "not declared" (proto3 default semantics), not a tenant named `""`.

Where the interface declares nothing, a caller-supplied tenant is still useful and should be left alone — for example when connecting straight to an interface URL with no card to consult.

## Scope

- [ ] `A2AClient` carries an optional tenant, set by the factory from the selected interface, with the empty string treated as undeclared.
- [ ] All eleven request methods apply it: send, send-streaming, get/list/cancel task, subscribe, the four push-config methods, and get-extended-agent-card.
- [ ] The client's tenant overrides whatever the request carried; with no client tenant the request is passed through untouched and unclonned.
- [ ] Tests: a declared tenant reaches the wire on every method; a caller-set tenant is replaced by the declared one; an interface declaring none leaves a caller-set tenant intact; an empty declared string is treated as undeclared.

## Follow-up, tracked separately

`a2acli` currently resolves `--tenant` as `cli.tenant.or(interface.tenant)` — the flag wins — which is the same violation one layer up, and `--tenant` is not a flag the a2a-cli specification defines. Once the library applies the declared value, that plumbing should go.

## Credit

Found and first fixed by @arkavo-com in #80, which has since gone stale against `main`.

Guía de contribución

Abrir la guía de contribución

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.