a2aproject / a2aproject/a2a-rs
a2a-client: the selected AgentInterface's tenant is never echoed on requests (A2A §8.3.2)
- 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
Evaluación
Este issue todavía no se ha evaluado.