Skip to content

v0.3.0-alpha.1 (P0): A2A AgentCard types + AllowedDomains (Rust)#2

Merged
jaschadub merged 1 commit intodevfrom
feature/v0.3.0-a2a-types
May 1, 2026
Merged

v0.3.0-alpha.1 (P0): A2A AgentCard types + AllowedDomains (Rust)#2
jaschadub merged 1 commit intodevfrom
feature/v0.3.0-a2a-types

Conversation

@jaschadub
Copy link
Copy Markdown
Contributor

Summary

Implements the v0.3.0 surface that two downstream releases are blocked on:

  • Symbiont v1.8.0 Phase 3 (AgentPin-verified AgentCards, A2A auth middleware) was waiting on `A2aAgentCardResolver` + `LocalAgentCardStore` + signed AgentCard support.
  • SchemaPin v1.4.0 `A2aVerificationContext` (item 5 of the v1.4 roadmap) was waiting on `AllowedDomains` for tool-verification scoping.

Rust-only in this PR; JavaScript and Python ports follow in alpha.2.

What's new

Cross-protocol primitive

  • `AllowedDomains` (`types::discovery`) — typed wrapper over the list of domains an agent is permitted to interact with. Extracted from `Constraints::allowed_domains` via the new `Constraints::allowed_domains_typed()` helper. Empty list = no restriction (all domains trusted) per the convention SchemaPin v1.4 also uses. Includes `intersect()` for composing with cross-protocol callers, `allows()`, `unrestricted()`, `from_domains()`, and an idiomatic `FromIterator` impl.

Minimal A2A AgentCard subset (`types::a2a`)

Inline definition rather than depending on the upstream `a2a-types` crate while the A2A spec is still draft. The public surface lets us re-export from upstream once it stabilises without breaking callers.

  • `A2aAgentCard` — name, description, version, url, capabilities, skills, optional `agentpin` extension
  • `A2aAgentCapabilities` — streaming, push_notifications, allowed_domains
  • `A2aAgentSkill` — id, name, optional description
  • `AgentpinExtension` — agentpin_endpoint, public_key_jwk, signature

Signing + verification (`a2a` module)

  • `A2aAgentCardBuilder::from_declaration(url, decl).agentpin_endpoint("...").sign(&priv_pem, kid)` — turns an `AgentDeclaration` into a signed `A2aAgentCard`. Maps capabilities to skills via `capability_to_skill`; propagates `Constraints::allowed_domains` into `A2aAgentCapabilities::allowed_domains`. Detached ECDSA P-256 signature covers the canonical (sorted-key) bytes of the AgentCard with the extension cleared.
  • `verify_agentpin_extension(card)` — verifies the extension signature against the JWK embedded in it. Defends against tampering with name / url / capabilities / skills / agentpin_endpoint.

Two new resolvers

  • `LocalAgentCardStore` (`resolver_local`, always available) — in-memory store of pre-registered AgentCards keyed by AgentPin discovery domain. Implements `DiscoveryResolver`. Verifies the signature at register-time and pre-derives a `DiscoveryDocument` so the rest of the AgentPin verification stack runs unchanged. Supports Symbiont v1.7.0's push-based external-agent registration where the coordinator receives AgentCard JSON inline rather than fetching from `.well-known`.
  • `A2aAgentCardResolver` (`resolver_a2a`, gated on `fetch`) — fetches `https://{domain}/.well-known/agent-card.json`, verifies the AgentPin extension, cross-checks that the embedded `agentpin_endpoint` host matches the fetched domain (defends against a card pointing at another domain's AgentPin discovery), and derives a `DiscoveryDocument`. `last_card()` exposes the original A2A representation alongside the derived doc for callers that want both views.

Discovery doc field

  • `a2a_endpoint` on `DiscoveryDocument` — optional URL of the entity's A2A AgentCard endpoint, enabling cross-protocol discovery.

QA

Check Result
`cargo test -j2 --all-features` 153 passed (was 145; +8 `AllowedDomains` tests, plus 4 `types::a2a`, 7 `a2a` builder/sign/verify, 8 `resolver_local` folded into the lib total)
`cargo build -j2 --all-features` clean
`cargo clippy --all-features -j2 -- -D warnings` clean
`cargo fmt --check` clean
`python3 -m json.tool context7.json` valid

Backward compatibility

Purely additive — v0.2.0 callers are unaffected.

Reader ↓ / Writer → v0.2.0 doc v0.3.0 doc (no a2a fields) v0.3.0 doc (with a2a)
v0.2.0 reader works works works (`a2a_endpoint` ignored)
v0.3.0 reader works works works

`AgentCard` without an `agentpin` extension is structurally valid (extension is `Option`); `verify_agentpin_extension` errs on missing extension only when explicitly called.

Spec / docs

  • `CHANGELOG.md` — new `0.3.0-alpha.1` entry above the alpha.1 block.
  • `ROADMAP.md` — v0.3.0 marked Rust shipped (alpha.1); release-timeline row added.
  • `SKILL.md` — frontmatter `version: 0.3.0-alpha.1` + new `stable_version: 0.2.0` field, description expanded.
  • `context7.json` — description expanded with the new A2A surface.

Roadmap impact

  • Item v0.3.0 — Rust complete (alpha.1). JS/Python ports follow in alpha.2.
  • Unblocks SchemaPin v1.4.0 A2aVerificationContext (was waiting on `AllowedDomains`).
  • Unblocks Symbiont v1.8.0 Phase 3 (was waiting on AgentPin-verified AgentCards).

Test plan

  • All cargo features build and test green
  • Clippy + fmt clean
  • context7.json valid
  • Cross-language interop test (sign in Rust, verify in JS/Python) — deferred to alpha.2 when ports land
  • Real DNS / HTTP integration test for `A2aAgentCardResolver` against a controlled fixture — deferred (mirrors the SchemaPin v1.4 pattern; can land with the same fixture infra)

Implements the v0.3.0 surface that **Symbiont v1.8.0 Phase 3 and SchemaPin
v1.4.0's A2aVerificationContext both depend on**. Rust-only in this PR;
JavaScript and Python ports follow in alpha.2.

New surface (purely additive — v0.2.0 callers unaffected):

- AllowedDomains type (types::discovery): typed wrapper over the list of
  domains an agent is permitted to interact with. Extracted from
  Constraints::allowed_domains via the new
  Constraints::allowed_domains_typed() helper. Empty list = no restriction
  (all domains trusted). Includes intersect() for composing with
  cross-protocol callers — most importantly SchemaPin v1.4's
  A2aVerificationContext, which scopes tool verification to the
  intersection of caller and provider domains. Implements FromIterator.

- Minimal A2A AgentCard subset (types::a2a): A2aAgentCard,
  A2aAgentCapabilities, A2aAgentSkill plus the AgentPin-specific
  AgentpinExtension (agentpin_endpoint, public_key_jwk, signature).
  Inline definition rather than a dependency on the upstream a2a-types
  crate while the A2A spec is still draft — the public surface lets
  us re-export from upstream once it stabilises without breaking
  callers.

- A2aAgentCardBuilder (a2a module): turns an AgentDeclaration into a
  signed A2aAgentCard. Maps capabilities to skills via
  capability_to_skill, propagates Constraints::allowed_domains into
  A2aAgentCapabilities::allowed_domains. Detached ECDSA P-256 signature
  covers the canonical (sorted-key) bytes of the AgentCard with the
  extension cleared. with_skill_overrides() lets callers supply richer
  skill names/descriptions than the raw capability strings.

- verify_agentpin_extension(card): verifies the extension signature
  against the JWK embedded in the extension. Defends against tampering
  with name / url / capabilities / skills / agentpin_endpoint.

- LocalAgentCardStore (resolver_local module): in-memory store of
  pre-registered AgentCards keyed by their AgentPin discovery domain.
  Implements DiscoveryResolver (always available, no `fetch` feature).
  Verifies the signature at register-time and pre-derives a
  DiscoveryDocument so the rest of the AgentPin verification stack
  runs unchanged. Supports Symbiont v1.7.0's push-based external-agent
  registration flow.

- A2aAgentCardResolver (resolver_a2a module, gated on `fetch`):
  fetches https://{domain}/.well-known/agent-card.json, verifies the
  AgentPin extension, cross-checks that the embedded agentpin_endpoint
  host matches the fetched domain (defends against a card pointing at
  another domain's AgentPin discovery), and derives a
  DiscoveryDocument. last_card() exposes the original A2A
  representation for callers that want both views.

- a2a_endpoint field on DiscoveryDocument — optional URL of the
  entity's A2A AgentCard endpoint, enabling cross-protocol discovery.

QA:

- 153 lib tests pass (was 145; +8 AllowedDomains, +4 types::a2a, +7
  a2a builder, +8 resolver_local — net new tests are folded into
  the lib total).
- cargo build / cargo test --all-features green.
- cargo clippy --all-features -j2 -- -D warnings clean.
- cargo fmt --check clean.

Spec / docs:

- CHANGELOG: new 0.3.0-alpha.1 entry.
- ROADMAP: v0.3.0 marked Rust shipped (alpha.1); release-timeline row
  added.
- SKILL.md: frontmatter version bumped to 0.3.0-alpha.1, stable_version
  field added (0.2.0).
- context7.json: description expanded with v0.3.0-alpha A2A surface +
  trust-stack position.

Backward compatibility:

- DiscoveryDocument without `a2a_endpoint` and AgentCards without
  `agentpin` extension behave exactly as v0.2.0.
- All existing 145 tests still pass unchanged.

Roadmap impact:

- Item v0.3.0 — Rust complete. JS/Python ports follow in alpha.2.
- Unblocks SchemaPin v1.4.0 A2aVerificationContext (item 5 of v1.4
  roadmap, was waiting on AllowedDomains).
- Unblocks Symbiont v1.8.0 Phase 3 (AgentPin-verified AgentCards).
@jaschadub jaschadub force-pushed the feature/v0.3.0-a2a-types branch from 046c00a to a73519e Compare May 1, 2026 19:31
@jaschadub jaschadub merged commit 162a233 into dev May 1, 2026
0 of 6 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.

1 participant