Skip to content

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

Merged
jaschadub merged 1 commit into
devfrom
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 into
devfrom
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

CheckResult
`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 docv0.3.0 doc (no a2a fields)v0.3.0 doc (with a2a)
v0.2.0 readerworksworksworks (`a2a_endpoint` ignored)
v0.3.0 readerworksworksworks

`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
jaschadubforce-pushed the feature/v0.3.0-a2a-types branch from 046c00a to a73519eCompareMay 1, 2026 19:31
@jaschadub
jaschadub merged commit 162a233 into devMay 1, 2026
0 of 6 checks passed
@jaschadub
jaschadub deleted the feature/v0.3.0-a2a-types branch May 16, 2026 04:37
Sign up for freeto 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

@jaschadub