Skip to content

v0.5.1: Security patch, SEO improvements, and gRPC build tooling#82

Merged
tomtom215 merged 7 commits into
mainfrom
claude/update-rustls-webpki-CUIUy
Apr 15, 2026
Merged

v0.5.1: Security patch, SEO improvements, and gRPC build tooling#82
tomtom215 merged 7 commits into
mainfrom
claude/update-rustls-webpki-CUIUy

Conversation

@tomtom215
Copy link
Copy Markdown
Owner

Summary

This release addresses a critical TLS security vulnerability, significantly improves documentation SEO and discoverability, and eliminates the need for manual protoc installation by vendoring the protobuf compiler.

Key Changes

Security

  • rustls-webpki 0.103.12 upgrade — Fixes RUSTSEC-2026-0098 (GHSA-965h-392x-2mh5), a URI name constraints bypass in X.509 path validation. Reaches a2a-protocol-client transitively via rustls.

Documentation & SEO

  • Enhanced sitemap.xml — Added comprehensive comments explaining the single-date strategy and sync requirement with SUMMARY.md. Expanded from 43 to 71 lines with detailed guidance.
  • Improved head.hbs metadata — Expanded from 35 to 112 lines with:
    • Detailed comments explaining canonical URL rewriting via JavaScript (necessary because mdbook's Handlebars pipeline lacks string manipulation)
    • Twitter Card upgraded to summary_large_image for better preview rendering
    • Open Graph image metadata (1200×630px)
    • JSON-LD structured data for project-level schema.org markup
    • Enhanced robots meta tags with snippet/preview size hints
    • Inline script that rewrites canonical/og:url/twitter:url to current page URL for Chromium-based crawlers (Googlebot, Bingbot)
  • robots.txt expansion — Added detailed comments explaining crawl policy, AI training opt-in rationale, and explicit disallow for mdbook's print.html
  • Benchmark dashboard — Vendored Chart.js 4.5.1 to eliminate CDN dependency (corporate proxies block cdn.jsdelivr.net); added book/static/vendor/README.md with upstream tracking
  • API reference updates — Clarified serve() and serve_with_addr() as async functions with return types; added three new TenantResolver implementations to the built-in types table

Build Tooling

  • Protobuf compiler vendoring — Replaced arduino/setup-protoc GitHub Action with protoc-bin-vendored crate:
    • Removed protoc installation steps from CI workflows (release.yml, ci.yml, coverage.yml, mutants.yml)
    • Updated build.rs in both a2a-client and a2a-server to use vendored binary when PROTOC env var is unset
    • Supports Linux (x86/aarch64), macOS (x86/aarch64), and Windows x86_64 out of the box
    • Contributors can now cargo build --features grpc on a clean machine without manual protobuf-compiler installation
  • gRPC dependency updates — Upgraded tonic/prost ecosystem from 0.12/0.13 to 0.14; added tonic-prost and tonic-prost-build crates

Code Quality

  • TLS integration tests — Updated to use explicit rustls::crypto::ring::default_provider() via builder_with_provider() to handle multi-provider scenarios (fixes panic when both ring and aws-lc-rs are in the dependency graph)
  • OpenTelemetry SDK compatibility — Updated metric collection code to match new SDK API (0.24+):
    • Changed metric.data.as_any().downcast_ref::<Sum<u64>>() to pattern-match on AggregatedMetrics::U64(MetricData::Sum(...))
    • Updated method calls: scope_metrics(), metrics(), metric.name(), metric.data(), sum.data_points(), SumDataPoint::value()
    • Changed shutdown() to shutdown_with_timeout()
  • WebSocket message handling — Converted string-to-WsMessage sends to use .into() for type coercion (e.g., WsMessage::Text(json.into()))
  • Benchmark imports — Migrated from

https://claude.ai/code/session_01GnfmC2YVHMP1SJVwzjAccC

claude added 7 commits April 15, 2026 09:53
Fixes RUSTSEC-2026-0098 / GHSA-965h-392x-2mh5: rustls-webpki
incorrectly accepted URI name constraints during X.509 path validation,
potentially letting a CA bypass its own URI NameConstraints for
certificates it should have been restricted from issuing.

a2a-protocol-client pulls rustls-webpki transitively via rustls ->
hyper-rustls / tokio-rustls when the tls-rustls feature is enabled.
Practical exploitability requires a trust chain whose CA uses URI name
constraints (uncommon in the public Web PKI via webpki-roots, but
possible for private/enterprise CA bundles).

Release as a dedicated patch so downstreams can pick up the fix without
waiting for unrelated in-flight work on 0.5.0:

- cargo update -p rustls-webpki --precise 0.103.12
- bump all four publishable crates 0.5.0 -> 0.5.1
  (a2a-protocol-types, a2a-protocol-client, a2a-protocol-server,
  a2a-protocol-sdk) and their cross-references
- CHANGELOG.md: new [0.5.1] - 2026-04-15 Security entry mirroring the
  earlier RUSTSEC-2026-0049 note, above the still-staged [0.5.0]
- book/src/reference/changelog.md: matching v0.5.1 section
- CITATION.cff: version 0.5.1, date-released 2026-04-15
Applies cargo update to pull every semver-compatible patch/minor bump:

  axum 0.8.8 -> 0.8.9
  bitflags 2.11.0 -> 2.11.1
  cc 1.2.57 -> 1.2.60
  fastrand 2.3.0 -> 2.4.1
  hashbrown 0.16.1 -> 0.17.0
  hyper 1.8.1 -> 1.9.0
  hyper-rustls 0.27.7 -> 0.27.9
  icu_* 2.1.x -> 2.2.0
  indexmap 2.13.0 -> 2.14.0
  iri-string 0.7.10 -> 0.7.12
  itoa 1.0.17 -> 1.0.18
  js-sys 0.3.91 -> 0.3.95
  libc 0.2.183 -> 0.2.185
  libredox 0.1.14 -> 0.1.16
  litemap 0.8.1 -> 0.8.2
  mio 1.1.1 -> 1.2.0
  num-conv 0.2.0 -> 0.2.1
  pkg-config 0.3.32 -> 0.3.33
  potential_utf 0.1.4 -> 0.1.5
  proptest 1.10.0 -> 1.11.0
  rand 0.9.2 -> 0.9.4
  rayon 1.11.0 -> 1.12.0
  redox_syscall 0.7.3 -> 0.7.4
  rustc-hash 2.1.1 -> 2.1.2
  rustls 0.23.37 -> 0.23.38
  semver 1.0.27 -> 1.0.28
  tinystr 0.8.2 -> 0.8.3
  tokio 1.50.0 -> 1.52.0
  tokio-macros 2.6.1 -> 2.7.0
  unicode-segmentation 1.12.0 -> 1.13.2
  uuid 1.22.0 -> 1.23.0
  wasm-bindgen 0.2.114 -> 0.2.118 (and friends)
  web-sys 0.3.91 -> 0.3.95
  zerocopy 0.8.42 -> 0.8.48
  zerofrom 0.1.6 -> 0.1.7
  zerotrie 0.2.3 -> 0.2.4
  zerovec 0.11.5 -> 0.11.6
  writeable 0.6.2 -> 0.6.3
  yoke 0.8.1 -> 0.8.2
  (+ assorted Windows target-metadata crates)

Verification:
- cargo check --workspace --all-targets: clean
- cargo check --workspace --all-features --all-targets: clean
  (requires protobuf-compiler on PATH for grpc feature)
- cargo test --workspace: all 64 suites pass, zero failures

Out-of-semver bumps (criterion, tonic/tonic-build/prost, opentelemetry
stack, tokio-tungstenite, rcgen, genai, socket2) are evaluated in
follow-up commits so each breaking-change group is reviewable in
isolation.
…enite, criterion, rcgen, genai, socket2

Brings every remaining dependency to its latest upstream version, with
source migrations where the new version introduced breaking changes.
Each group was verified with the relevant feature flags enabled and the
full workspace test suite (64 suites, ~1.4k assertions) passing.

tonic / prost / tonic-build 0.12 -> 0.14 (grpc feature)
  - tonic-build 0.14 split off the prost integration into a separate
    `tonic-prost-build` crate (for build scripts) and `tonic-prost`
    crate (for the runtime codec). Updated both build.rs files to
    `tonic_prost_build::configure()` and added `tonic-prost` as a
    runtime dep alongside `tonic`. Application code in
    `transport/grpc.rs` and `dispatch/grpc.rs` needed no changes — the
    public tonic::* surface is stable across 0.12 -> 0.14 for our
    usage (Channel, Status, Code, Request, Streaming, metadata).

opentelemetry stack 0.28 -> 0.31 (otel feature)
  - No changes needed in `src/otel/mod.rs` or `src/otel/pipeline.rs`
    production code — MetricExporter, PeriodicReader, SdkMeterProvider
    and Resource builders are unchanged.
  - `data::ResourceMetrics` and friends in 0.31 switched from public
    fields to iterator accessors (`.scope_metrics()`, `.metrics()`,
    `.name()`, `.data()`, `.data_points()`, `.value()`, `.count()`,
    and a concrete `AggregatedMetrics` enum instead of
    `dyn Any + downcast_ref::<Sum<T>>()`). Rewrote the observable-effect
    tests to the iterator API, which is also more type-safe.
  - The `ManualReader` + `MetricReader` trait used by the tests was
    moved behind the `experimental_metrics_custom_reader` feature, so
    that feature is now enabled on the `opentelemetry_sdk` dep (with
    a comment explaining it is only needed for tests and adds no
    runtime cost). The `MetricReader::shutdown` method was replaced
    with `shutdown_with_timeout` — test impl updated accordingly.

tokio-tungstenite 0.24 -> 0.29 (websocket feature)
  - `WsMessage::Text(String)` became `WsMessage::Text(Utf8Bytes)`.
    Added `.into()` at every send site in websocket transport and
    dispatcher (production + tests). Receive-side code paths already
    work via `Deref<Target = str>` on `Utf8Bytes`.
  - `WsMessage::Ping(Vec<u8>)` became `WsMessage::Ping(bytes::Bytes)`.
    Added `.into()` at both ping test sites.
  - `Message::into_text()` now returns `Utf8Bytes` instead of `String`;
    updated the `read_text` test helper to `.as_str().to_owned()`.

criterion 0.5 -> 0.8 (benchmarks, dev-dep)
  - No API breakage in the benchmark harness itself.
  - `criterion::black_box` is deprecated in favour of
    `std::hint::black_box`. Migrated all 87 call sites across 12
    benchmark files (import change plus fully-qualified-path
    substitution). Zero deprecation warnings remaining.

rcgen 0.13 -> 0.14 (TLS integration tests, dev-dep)
  - `CertificateParams::signed_by()` now takes `&Issuer<'_, _>` instead
    of separate `(&Certificate, &KeyPair)`. Construct `Issuer::new(
    params, key)` once and reuse it across child-cert signings in the
    TLS and mTLS test helpers. All 7 TLS integration tests still pass.

genai 0.1 -> 0.5 (genai-agent example)
  - `ChatResponse::content_text_as_str` deprecated in favour of
    `first_text`. Updated the one call site.

socket2 0.5 -> 0.6 (benchmarks, transitive)
  - No source changes needed; patch-only bump for the benchmark
    server's SO_REUSEADDR path.

Verification matrix (all passing, 0 failures across the board):
  cargo test --workspace                         64 suites
  cargo test --workspace --features websocket    64 suites
  cargo test --workspace --features grpc         64 suites
  cargo test -p a2a-protocol-server --features otel      33 suites
  cargo test -p a2a-protocol-server --features sqlite    33 suites
  cargo test -p a2a-protocol-server --features postgres  28 suites
  cargo test -p a2a-protocol-client --features tls-rustls --test tls_integration_tests  7/7
  cargo check --workspace --benches                      clean, zero warnings
  cargo bench -p a2a-protocol-types --bench json_serde -- --test  ok
The benchmark dashboard at /reference/benchmark-dashboard.html used to
load Chart.js 4 from cdn.jsdelivr.net and Space Grotesk + JetBrains Mono
from fonts.googleapis.com. Both domains are routinely blocked by
corporate egress proxies and Pi-hole-style DNS filters, and the dashboard
would render with an invisible font-fallback chain and a missing Chart
constructor — the symptom that triggered the "site blocked on corp
networks" bug report. Vendoring the assets sidesteps the entire class
of problem: every request now goes to the same origin as the HTML.

Phase 3a — corp-network blocker fix
-----------------------------------
- Vendor Chart.js 4.5.1 UMD build at `book/static/vendor/chart.umd.min.js`
  (209 KB, MIT-licensed, one pinned version), with a `vendor/README.md`
  that documents the upstream, license, and the one-liner to re-vendor
  a new upstream release.
- Drop the Google Fonts `<link>` and `preconnect` entirely from
  `benchmark-dashboard.html`. Replace the Space Grotesk / JetBrains Mono
  families with system-ui / ui-monospace stacks (Apple, Segoe UI, Roboto,
  SF Mono, Menlo, Consolas, Liberation Mono). System fonts render on
  every OS without a network round-trip. An inline comment explains the
  rationale so a future contributor does not "fix" it by re-adding the
  CDN reference.
- Point the `<script>` tag at `../vendor/chart.umd.min.js`, a relative
  same-origin path that works whether the site is served at
  https://a2a-rust.com/, at a preview URL, or opened from disk.
- `.github/workflows/docs.yml` now copies the entire `book/static/`
  tree into the built site (not just robots.txt + sitemap.xml), so
  `vendor/` ships automatically. The CI step asserts that the critical
  files land where they are expected — catching a broken deploy at
  build time rather than in production.
- New `book/static/vendor/README.md` documents the vendoring policy.

Phase 3b — SEO polish
---------------------
- Add `site-url = "https://a2a-rust.com/"` to `book.toml` so mdbook emits
  canonical URLs and absolute asset paths consistently across pages. The
  value must stay in sync with `theme/head.hbs` og:url, the sitemap
  <loc> entries, and the robots.txt `Sitemap:` directive — the comment
  in `book.toml` spells this out.
- Strengthen `theme/head.hbs`:
  - `robots` meta now includes `max-snippet:-1,max-image-preview:large,
    max-video-preview:-1` so Google/Bing can show full descriptions and
    large preview cards in SERPs.
  - Twitter card upgraded from `summary` to `summary_large_image`
    (renders with a full-width preview on X, LinkedIn, Slack, Discord).
  - Open Graph now emits `og:image` (1200x630) with width/height/alt
    attributes, `og:locale`, and structured alternate `hreflang` links
    (en + x-default) so the canonical URL is unambiguous.
  - JSON-LD structured data expanded: adds `@type SoftwareSourceCode`
    author block, `ComputerLanguage` reference for Rust, explicit
    `applicationCategory DeveloperApplication`, `operatingSystem` list,
    and a curated `keywords` array. This is what drives developer-tool
    carousels on Google search.
- Regenerate `sitemap.xml` with 42 URLs (was 41 — adds
  `/reference/dashboard.html` and `/reference/benchmark-dashboard.html`
  which were missing), bumped lastmod to 2026-04-15, and adds
  `changefreq` hints (weekly for frequently-updated pages like the
  benchmarks and dashboard, monthly for stable reference material).
- Expand `robots.txt` with a documented policy (indexing open, 1s
  crawl-delay for courtesy, AI training crawlers allowed per Apache-2.0
  license, explicit sitemap discovery line).

Verification
------------
- `mdbook build book` → clean, no warnings.
- Simulated the CI `cp -r book/static/. book/book/` step locally:
  `book/book/vendor/chart.umd.min.js`, `robots.txt`, and `sitemap.xml`
  all land at the expected output paths.
- `grep -E '(href|src)=".*(fonts.googleapis|jsdelivr)'` against both
  source and built HTML returns nothing — zero external network
  requests remain on the benchmark dashboard page.
- Built pages now contain `og:image`, `summary_large_image`, JSON-LD
  `SoftwareSourceCode`, canonical + alternate hreflang links.

Scope notes
-----------
- The young `.com` domain age (registered ~2026-03-30, ~2 weeks old
  at the time of this commit) is a secondary cause of corp-network
  friction and is not fixable from inside the repo. It resolves
  naturally as domain reputation builds.
- No GSC / Bing verification files are committed — those are
  account-scoped secrets the repo owner should drop into
  `book/static/` separately (they ship as-is via the new
  blanket static-copy step).
…re trait, context_id placement, Part::url

Systematic audit of every high-risk book page against the 0.5.1 source.
Most pages were accurate; this commit fixes the specific drift found.

api-reference.md
  - `serve(addr, dispatcher)` and `serve_with_addr(addr, dispatcher)`
    were labelled "blocking" — they are `async fn`s. Replace the one-
    line description with the full return type and an explicit `async`
    marker so readers understand they must be `.await`ed.
  - Streaming types table didn't distinguish `EventQueueWriter` /
    `EventQueueReader` (traits) from `EventQueueManager`, `EventEmitter`,
    `InMemoryQueueWriter`, `InMemoryQueueReader` (structs). Added a
    `Kind` column and rewrote the descriptions.
  - Built-in implementations table was missing the three built-in
    `TenantResolver` impls (`HeaderTenantResolver`,
    `BearerTokenTenantResolver`, `PathSegmentTenantResolver`) and the
    built-in `HttpPushSender`. Added all four with accurate feature-gate
    annotations.

handler.md
  - `HandlerLimits` table was missing `max_context_locks` (default
    10,000). The field is real — `crates/a2a-server/src/handler/limits.rs:51`
    — but undocumented. Added the row.
  - The "Custom Task Stores" example described the trait methods as
    `get, put, list, delete`. The actual `TaskStore` trait uses
    `save, get, list, insert_if_absent, delete`. Fixed the comment.
    (`put` has never existed; `save` has always been the method, and
    `insert_if_absent` is required for single-flight task creation.)

configuration.md
  - Same `max_context_locks` omission as `handler.md`. Added the row.

first-agent.md
  - Removed a non-existent `context_id: None` field from a
    `MessageSendParams` struct literal. `MessageSendParams` has only
    `{tenant, message, configuration, metadata}`; the `context_id`
    lives on the inner `Message` struct. The bad snippet would have
    compiled only with a `..Default::default()` escape hatch, which
    was not present.

sending-messages.md
  - Four occurrences of the same `context_id` placement bug as
    above. The continuation-of-conversation example was particularly
    misleading — it declared `context_id` at both the `Message`
    level *and* the `MessageSendParams` level. Now it lives on the
    `Message` only, as the wire format requires.
  - Replaced `Part::file_uri("https://...")` (a 0.3.x back-compat alias
    kept only so old code still compiles) with the idiomatic v1.0
    `Part::url("https://...").with_media_type("image/png")`. Both
    produce the same wire JSON, but the book should show the
    forward-compatible API.

crates/a2a-server/src/builder.rs
  - `RequestHandlerBuilder::with_event_queue_capacity` rustdoc said
    "Defaults to 64 items". The actual default is
    `DEFAULT_QUEUE_CAPACITY = 256` — the per-event cost inflection
    bump described in the 0.5.0 performance work. The value in code
    is correct and the book already says 256; only the rustdoc on the
    builder method was stale. Fix it and link to the canonical
    constant so the next bump stays in sync.

Pages audited and verified drift-free (read in full, every type /
method / field cross-checked against 0.5.1 source — no changes):

  building-agents/executor.md
  building-agents/stores.md                  (&Task breaking change already reflected)
  building-agents/dispatchers.md
  building-agents/interceptors.md
  building-agents/push-notifications.md
  concepts/protocol-overview.md
  concepts/transport-layers.md
  concepts/tasks-and-messages.md
  concepts/agent-cards.md
  concepts/streaming.md
  getting-started/installation.md            (MSRV 1.93 matches Cargo.toml:22)
  getting-started/quick-start.md
  getting-started/project-structure.md
  client/builder.md
  client/streaming.md
  client/task-management.md
  client/error-handling.md

Verification
  cargo check --workspace: clean, zero warnings
  cargo check -p a2a-protocol-server (builder.rs rustdoc link resolved)
  No .md file is pulled into a doctest via include_str!, so the book
  changes cannot break compilation — only the builder.rs rustdoc
  touched by this commit affected the build.
…r, protoc auto-vendor

Fixes every real CI failure surfaced on the
`claude/update-rustls-webpki-CUIUy` branch's first push, and removes
the "needs system protoc" foot-gun that was gating every contributor's
`cargo build --features grpc`. All 8 CI clippy combinations and all 9
CI test combinations now pass locally, matching the full
`.github/workflows/ci.yml` matrix.

1. Format (black_box import order)
----------------------------------
The dep-bump commit migrated 87 `criterion::black_box` call sites to
`std::hint::black_box` across 12 bench files. The sed that added the
`use std::hint::black_box;` import put it before `use criterion::...`,
which violates rustfmt's `Imports granularity = Module` ordering. Six
files were affected. Auto-fixed with `cargo fmt --all` and verified
clean with `cargo fmt --all --check`.

2. rustls CryptoProvider ambiguity under `--all-features` (the real
   1.93-matrix failure)
---------------------------------------------------------------------
Root cause: the `genai 0.1 -> 0.5` bump pulls in a new `reqwest` which
drags `rustls-platform-verifier` with the `aws-lc-rs` crypto provider.
With `cargo test --workspace --all-features`, feature unification
links *both* `ring` (from `a2a-protocol-client`'s explicit dep) and
`aws-lc-rs` into every test binary. rustls 0.23.38 then refuses to
pick one and panics at construction time:

  Could not automatically determine the process-level CryptoProvider
  from Rustls crate features.

Every test in `builder::*`, `client::*`, `discovery::*`,
`transport::*`, `tls::*` that constructs a `ClientConfig` through the
default builder hits this — 57 tests on the first pass, plus the 7
TLS integration tests on the second.

This is also exactly why the "Test (1.93, ubuntu-latest)" CI job
exited 101 (Rust's panic exit code). It is not a rust 1.93 MSRV issue
— it is a feature-unification issue that the branch would have hit
on any rust version.

Fix (source-level, no CI-only workaround):

- `crates/a2a-client/src/tls.rs`: switch `default_tls_config` and
  `tls_config_with_extra_roots` from `ClientConfig::builder()` to
  `ClientConfig::builder_with_provider(ring_provider())` where
  `ring_provider()` returns
  `Arc::new(rustls::crypto::ring::default_provider())`. This makes
  every production TLS handshake deterministic regardless of what
  other providers exist in the dep graph. Added `# Panics` docs
  explaining that the `.expect("...")` is unreachable for every
  `ring` version this crate pins against.

- `crates/a2a-client/tests/tls_integration_tests.rs`: apply the same
  pattern to every `ServerConfig::builder()`, `ClientConfig::builder()`
  and `WebPkiClientVerifier::builder()` call via `..._with_provider`,
  using a new `test_ring_provider()` helper. Comment explains the
  feature-unification interaction for future maintainers.

Result: `cargo test --workspace --all-features` → 64 suites, 0
failures (was 64 failures on this step before).

3. Clippy redundant-closure warnings from the 0.31 opentelemetry
   test rewrite
----------------------------------------------------------------
`redundant_closure_for_method_calls` flags
`.map(|dp| dp.value())` → `.map(SumDataPoint::value)`. Clippy only
fires this at the `--all-features` edge because that is the only combo
that compiles the `otel` observable-effect tests. Replaced three
closures with method-path references in `crates/a2a-server/src/otel/mod.rs`
and pulled `SumDataPoint`, `HistogramDataPoint`, `GaugeDataPoint` into
scope via the existing `opentelemetry_sdk::metrics::data` import.

4. Missing `# Panics` doc sections (all-features clippy)
--------------------------------------------------------
`clippy::missing_panics_doc` — added `# Panics` sections to
`default_tls_config` and `tls_config_with_extra_roots` documenting
that the `.expect("ring provider supports default protocol versions")`
is unreachable in practice.

5. protoc auto-install via `protoc-bin-vendored`
------------------------------------------------
The grpc feature used to require contributors to `apt-get install
protobuf-compiler` / `brew install protobuf` / choco before
`cargo build --features grpc` would work, and CI installed it via the
`arduino/setup-protoc` action (+ a GitHub token). Running
`cargo check --features grpc` on a clean machine would panic in
build.rs with a `Could not find 'protoc'` error and no actionable
recovery path for the user.

Fix:

- Added `protoc-bin-vendored = { version = "3" }` to `[workspace.dependencies]`.
  This crate ships pre-built `protoc` binaries for Linux
  x86_64/aarch64/ppcle/s390, macOS x86_64/aarch64, and Windows x86_64
  — covering every platform in the CI matrix and every architecture
  a normal Rust contributor runs.

- Both `crates/a2a-client/build.rs` and `crates/a2a-server/build.rs`
  now set `PROTOC` to the vendored binary path at build-script start,
  *only if the user has not already set `PROTOC` themselves*, so CI
  jobs that want to pin a system protoc can still do so:

      if std::env::var_os("PROTOC").is_none() {
          if let Ok(path) = protoc_bin_vendored::protoc_bin_path() {
              std::env::set_var("PROTOC", path);
          }
      }

- Wired `protoc-bin-vendored` into each crate's `[build-dependencies]`
  as an `optional = true` dep gated on the `grpc` feature, so the
  vendored binary is only pulled in when grpc is enabled (no cost for
  default-feature builds).

- Removed every `arduino/setup-protoc` step from `ci.yml`, `coverage.yml`,
  `mutants.yml`, and `release.yml` (four occurrences in release.yml
  alone). Replaced each with a one-line comment pointing at the
  vendored-crate fallback so future readers do not re-add it reflexively.

Verification

  $ which protoc && mv /usr/bin/protoc /tmp/protoc.bak
  $ cargo clean -p a2a-protocol-server -p a2a-protocol-client
  $ cargo check -p a2a-protocol-server -p a2a-protocol-client --features grpc
      [...]
      Checking tonic v0.14.5
      Checking tonic-prost v0.14.5
      Finished `dev` profile [unoptimized + debuginfo] target(s) in 24.96s

With `protoc` completely absent from `PATH` the grpc feature builds
cleanly via the vendored binary, proving the fallback works.

Final verification matrix (all passing)
---------------------------------------

  cargo fmt --all --check                                                 ok
  cargo clippy --workspace --all-targets                             -- -D warnings   ok
  cargo clippy --workspace --all-targets --features signing         -- -D warnings   ok
  cargo clippy --workspace --all-targets --features tracing         -- -D warnings   ok
  cargo clippy -p a2a-protocol-client --all-targets --features tls-rustls -- -D warnings   ok
  cargo clippy -p a2a-protocol-server --all-targets --features sqlite     -- -D warnings   ok
  cargo clippy -p a2a-protocol-server --all-targets --features postgres   -- -D warnings   ok
  cargo clippy -p a2a-protocol-server --all-targets --features axum       -- -D warnings   ok
  cargo clippy --workspace --all-targets --all-features              -- -D warnings   ok
  cargo test  --workspace                                    64 suites / 0 failures
  cargo test  --workspace --features signing                64 suites / 0 failures
  cargo test  --workspace --features tracing                64 suites / 0 failures
  cargo test  -p a2a-protocol-client --features tls-rustls  11 suites / 0 failures
  cargo test  -p a2a-protocol-server --features sqlite      33 suites / 0 failures
  cargo test  -p a2a-protocol-server --features postgres    33 suites / 0 failures
  cargo test  -p a2a-protocol-server --features axum        33 suites / 0 failures
  cargo test  --workspace --all-features                    64 suites / 0 failures
  cargo test  --workspace --no-default-features             64 suites / 0 failures
  RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps           ok

Scope notes
-----------
- The `Node.js 20 is deprecated` warnings on every CI step are a
  GitHub Actions ecosystem deprecation unrelated to this branch. They
  affect every workflow using `actions/checkout@v4.3.1`. Ignored here;
  fix separately at the org/repo level.
- Did NOT push to main, publish to crates.io, or create a PR — the
  user is handling the PR in a fresh session after this lands.
Addresses two Google Search Console findings surfaced after the first
dep-bump push:

1. "Duplicate without user-selected canonical" on
   https://a2a-rust.com/introduction.html (GSC, first seen 2026-03-17,
   1 affected page)
---------------------------------------------------------------------
Root cause I introduced in the previous head.hbs rewrite: the
`<link rel="canonical">`, `<meta property="og:url">`, and
`<meta name="twitter:url">` tags were all hardcoded to
`https://a2a-rust.com/`. Every single page in the book was claiming
to be the canonical of the site root, so Google saw
`/introduction.html` and `/` as duplicates with no usable canonical
signal and flagged the page.

A clean server-side fix would compute the canonical from `{{path}}`
via Handlebars, but mdbook's Handlebars pipeline does not register
any string-manipulation helpers (only `toc` and `fa`) and the path
variable comes in as e.g. `introduction.md` — there is no built-in
way to rewrite `.md` → `.html` from a user `head.hbs` override.

Fix: keep the hardcoded tags as seed values pointing at the root,
give them stable `id` attributes, and rewrite them at page load
time from `location.pathname` via a tiny synchronous inline script:

    var canonicalUrl = "https://a2a-rust.com" + location.pathname;
    var canonicalEl = document.getElementById("a2a-canonical");
    if (canonicalEl) canonicalEl.href = canonicalUrl;
    // ...same for og:url and twitter:url

Both Googlebot and Bingbot run Chromium and pick up JS-written
canonicals in their rendering pass, so this works for the search
engines that matter. Long-tail non-JS crawlers fall back to the
site-root canonical — strictly better than the pre-fix state where
every page had a *wrong* per-page canonical.

The JSON-LD `SoftwareSourceCode.url` legitimately describes the
project as a whole and is left pointing at the root — it is not a
per-page canonical.

2. "Excluded by 'noindex' tag" on https://a2a-rust.com/print.html
   (GSC, first seen 2026-03-21, 1 affected page)
-----------------------------------------------------------------
This was working as intended: mdbook emits `print.html` as a "print
this book" aggregate view and marks it `<meta name="robots"
content="noindex">` because it would dilute per-page SEO. GSC
surfaces this as informational.

Polish: add `Disallow: /print.html` to `book/static/robots.txt` so
crawlers do not waste budget fetching the page in the first place,
and the "Excluded by noindex" warning will clear from GSC on the
next crawl pass. Comment in robots.txt explains why and notes that
the page is still reachable by humans who click "Print this book"
in the sidebar (that link does a `window.print()` not a crawl).

On the third GSC finding ("Page with redirect")
-----------------------------------------------
Three URLs (`http://a2a-rust.com/`, `http://www.a2a-rust.com/`,
`https://www.a2a-rust.com/`) are flagged as "Page with redirect".
This is informational — it means Google hit them, they redirected
correctly to `https://a2a-rust.com/` (the canonical), and Google
indexed the canonical. This is the *correct* behaviour for HTTP→HTTPS
and www→apex redirects. No fix is needed; clicking "Validate Fix"
in GSC after the next crawl will re-verify and clear the status.

Verification
------------
- `mdbook build book`: clean, no warnings.
- Built `book/book/introduction.html` contains the JS rewrite block
  (3 element IDs resolved), plus `<link rel="canonical">` seed.
- `grep "Disallow: /print.html" book/static/robots.txt` → present.
- All per-feature clippy/test matrices from the sibling CI-fix commit
  still pass (no production source touched).
@tomtom215 tomtom215 merged commit 6816836 into main Apr 15, 2026
40 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants