a2aproject / a2aproject/A2A

[Bug]: §5.5 states only the emit side of ProtoJSON — ADR-001's strictness requirement (accept integer enums and snake_case field names) is unstated

オープン
#2,087 コメント 2 件 リアクション 0 件 担当者 0 名 GitHub で見る
主要言語
Shell
スター
25.7k
フォーク
2.6k
平均マージ
3日 6時間
マージ済み PR(30日)
16

説明

### What happened?

§5.5 specifies the **serialize** direction for enums but not the **accept** direction, and ADR-001 resolves that gap in a way the specification never states. The result is that two implementations can both believe they conform while refusing each other's traffic.

§5.5 says:

> Enum values MUST be represented according to the ProtoJSON specification, which serializes enums as their string names as defined in the Protocol Buffer definition (typically SCREAMING_SNAKE_CASE).

Read on its own, that constrains what an implementation **emits**. It says nothing about what an implementation must **accept**, and the relative clause ("which serializes…") describes only the encoding half of ProtoJSON.

[ADR-001](https://github.com/a2aproject/A2A/blob/main/adrs/adr-001-protojson-serialization.md) is stricter than that. Under Consequences → Neutral:

> Implementations must follow ProtoJSON specification strictly

[ProtoJSON](https://protobuf.dev/programming-guides/json/) requires parsers to be lenient in two ways relevant here:

- **Enums** — "Parsers accept both enum names and integer values."
- **Field names** — parsers accept both `lowerCamelCase` and the original `snake_case` proto field name.

So under ADR-001 a conforming A2A server **MUST accept** `"role": 1` as equivalent to `"role": "ROLE_USER"`, and **MUST accept** `"message_id"` as equivalent to `"messageId"`. Neither requirement appears anywhere in the specification.

### Why this bites

An implementer working from `specification.md` — which is what anyone writing a client without a generated SDK does — will read §5.5, see one casing rule, and build a receiver that validates against it. That receiver rejects traffic from a conforming ProtoJSON-based peer. Nothing in the specification tells them they were wrong, and nothing in their own test suite catches it, because they generate and parse the same form consistently.

This is the shape of divergence that only appears when two independent implementations meet. We hit a closely related instance of it in the JS SDK (a2aproject/a2a-js#605), where two code paths in a single implementation canonicalized the same card differently and the SDK rejected a signature it had just produced.

The asymmetry also runs the other way: an implementation that *emits* `"role": 1` is non-conforming per §5.5 even though every ProtoJSON parser would accept it. Only stating the emit rule leaves that asymmetry invisible.

### Suggested fix

State the accept-side requirement in §5.5 alongside the emit-side one. Something like:

> Implementations MUST serialize enum values as their string names (typically SCREAMING_SNAKE_CASE) and MUST serialize field names as `lowerCamelCase`.
>
> Per ProtoJSON, implementations MUST also **accept** integer enum values in addition to enum names, and MUST accept the original proto field name (`snake_case`) in addition to `lowerCamelCase`. Receivers MUST NOT reject a message solely because it uses the alternative accepted form.

A link from §5.5 to ADR-001 would also help; the specification references "ADR-001" by name but does not link it, so the strictness requirement is hard to find from the section it governs.

Happy to send a PR for this wording if the direction is agreed.

### Two smaller questions

**1. Which error applies to an unsupported media type?** §5.4 maps `ContentTypeNotSupportedError` to `-32005` / `INVALID_ARGUMENT` / `400`. §3.3.2's validation guidance lists "unsupported content type" among its example scenarios next to `-32602 Invalid params`. Is `-32005` required, or are both conforming? We ask because a conformance check needs to know whether to assert one code or accept either.

**2. Must a `Part` carrying only an unrecognized member fail validation?** §4.1.6 says a `Part` MUST contain exactly one of `text`, `raw`, `url`, `data`. §5.7 says implementations SHOULD ignore unrecognized fields. Read together, a part like

```json
{ "content": { "$case": "data", "value": { "a": 1 } } }
```

should have `content` ignored, then present zero oneof members, and so fail validation. Is that the intended outcome?

That payload is not hypothetical: it is what a naive serialization of a generated tagged union produces in some languages, and an implementation that both writes and reads its own generated shape cannot detect it. Confirming the MUST makes the behaviour testable across implementations.

### Relevant log output

```shell
# Per ADR-001 ("Implementations must follow ProtoJSON specification strictly")
# both of these MUST be accepted as equivalent, but the specification
# only ever shows and mandates the second form:

{"role": 1, "messageId": "…", "parts": [{"text": "hi"}]}
{"role": "ROLE_USER", "messageId": "…", "parts": [{"text": "hi"}]}

# and per ProtoJSON field-name leniency:
{"role": "ROLE_USER", "message_id": "…", "parts": [{"text": "hi"}]}
```

### Code of Conduct

- [x] I agree to follow this project's Code of Conduct

コントリビューションガイド

コントリビューションガイドを開く

評価

この issue はまだ評価されていません。

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。