a2aproject / a2aproject/a2a-rs

a2acli: report a malformed invocation as an error envelope, not clap prose

Aperta
#193 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub
enhancement
Lingua principale
Rust
Stelle
75
Fork
19
Merge medio
11h 27m
PR unite (30g)
21

Descrizione

## Summary

A malformed invocation exits `2` correctly, but reports itself in clap's own prose rather than the Appendix B error envelope:

```console
$ a2acli --bogus-flag card get
error: unexpected argument '--bogus-flag' found

Usage: a2acli [OPTIONS]
```

§11.4 is explicit that this is a CLI-local failure and must be machine-readable:

> Errors MUST be machine-readable (the error envelope in Appendix B) […] **CLI-local failures** are the conditions the protocol has no opinion on, because they happen before or outside any request: **a malformed flag**, an unresolvable `--agent-card` reference, an unreadable local card, no network. Those carry a symbolic `A2ACLI_ERR_` identifier from Appendix D.

So `a2acli --bogus-flag` should print `{"error":{"code":"A2ACLI_ERR_USAGE",…}}` on stderr, the way every other usage error already does (`--endpoint` without a transport, a bad `--a2a-version`, a malformed `--data-part`).

The cause is that `run_args` uses `Cli::command().get_matches_from(args)`, which prints and exits inside clap before any of our own error handling runs.

## Why

This is the one error class a program is most likely to hit while being wired up — a flag renamed, a typo in a generated invocation, an agent harness passing something the tool does not accept. A consumer that parses stderr for the envelope (which §11.4 says it may rely on unconditionally) gets unparseable prose in exactly that case, and cannot distinguish "you invoked me wrongly" from a crash.

It was missed in the original #164 audit: the exit code is right, so the row looked satisfied.

## The subtlety

clap reports `--help` and `-V/--version` through the same `Err` channel, with `ErrorKind::DisplayHelp` / `DisplayHelpOnMissingArgumentOrSubcommand` / `DisplayVersion`. Those are **not** failures: they must keep printing to **stdout** and exiting `0` (`A2ACLI_CLI_001`). Only genuine parse failures become envelopes.

## Scope

- [ ] Switch `run_args` to `try_get_matches_from` and handle `clap::Error` ourselves.
- [ ] `DisplayHelp`, `DisplayHelpOnMissingArgumentOrSubcommand` and `DisplayVersion`: print clap's rendered output to stdout and exit `0`, unchanged from today.
- [ ] Every other `ErrorKind`: emit the Appendix B envelope with `A2ACLI_ERR_USAGE` on stderr and exit `2`.
- [ ] Keep clap's own message as the envelope's `message` (it names the offending argument precisely), flattened to one line, and populate `hint` pointing at `--help`.
- [ ] Never print clap's usage block alongside the envelope — §11.4 wants one object, and stdout must stay empty on failure (§11.1).
- [ ] Tests: an unknown flag, a missing required argument, and an invalid enum value each produce a parseable envelope with `A2ACLI_ERR_USAGE` and exit 2; `--help` and `--version` still write to stdout and exit 0; `help` as a subcommand likewise.

## Requirements closed

| ID | Requirement |
| --- | --- |
| `A2ACLI_OUT_004` | Errors are machine-readable and consistent: CLI-local failures carry an `A2ACLI_ERR_*` code (§11.4, App. B, App. D) — the malformed-flag case |

Fourth Tier 1 residual, alongside #178, #179 and #180.

Guida per i contributori

Apri la guida per i contributori

Valutazione

Questa issue non è ancora stata valutata.

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.