a2aproject / a2aproject/a2a-rs
a2a-client: the selected AgentInterface's tenant is never echoed on requests (A2A §8.3.2)
- Lingua principale
- Rust
- Stelle
- 75
- Fork
- 19
- Merge medio
- 11h 27m
- PR unite (30g)
- 21
Descrizione
## 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`.
Guida per i contributori
Apri la guida per i contributori
Direzione di ricerca
The issue is in the a2a-client crate. Start by examining the A2AClient struct and the A2AClientFactory::create_from_card_with_interface method to understand how the tenant field is currently handled. Review the eleven request methods listed in the issue to see where the tenant field needs to be applied. Write tests to verify that a declared tenant from the AgentInterface is correctly set on all requests, overrides any caller-supplied tenant, and that an empty string is treated as undeclared. The existing PR #80 may provide context but is stale.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Valutazione
- Stack tecnologico
- rust
- Ambito
- backend-api-design
- Tipo di issue
- Bug
- Difficoltà
- 3/5
- Tempo stimato
- 1-2 giorni
- Stato di attività
- Attiva
- Chiarezza
- Specificata chiaramente
- Idoneità per principianti
- 65/100