Skip to main content

vaid-a2a

Reference verifier for the VAID A2A extension specified in docs/a2a/v1/extension.md (draft).

A2A lets agents delegate work to agents. a2aproject/A2A#2028 proposes actorChain, a caller-supplied record of who acted for whom, and is explicit that a self-reported chain can narrow perfectly while proving no actual authority. This extension supplies one concrete, offline-checkable proof a hop can point at to fill that gap: a VAID — a signed, attenuated, revocable delegation document this repo already mints and verifies in three languages.

This package does not implement the extension's wire format; it implements the verification side: given an A2A Message (as a plain dict) and a trust configuration, it extracts any VAID(s) carried under the extension's metadata keys and runs the ordered check the spec defines, returning a typed allow/deny result with a reason — never a bare boolean, so a caller can log, alert, or retry the right thing.

Install

pip install -e python/vaid-pop -e python/vaid-mint -e python/vaid-a2a

(No published release yet — this is a draft extension on a feature branch. See docs/a2a/drafts/a2a-2028-reply.md and the branch's REPORT-306.md.)

Use

from datetime import datetime, timezone

from vaid_mint.revocation import InMemoryRevocationList
from vaid_a2a import verify_a2a_message, METADATA_VAID_KEY

trust_config = {
    "trustedIssuers": [
        {"trustDomain": "vaid.example", "kernelPublicKey": "<base64url, raw 32 bytes>"}
    ]
}

message = {
    "metadata": {
        METADATA_VAID_KEY: leaf_vaid_document,       # a dict, as produced by vaid_mint
        # METADATA_CHAIN_KEY: [ancestor_vaid_1, ...],  # optional, for chain checks
    }
}

result = verify_a2a_message(
    message,
    trust_config=trust_config,
    # A plain, never-populated InMemoryRevocationList() is UNAVAILABLE by
    # design (R.4.5/R.4.6), so this example as written DENIES EVERY REQUEST
    # with VerifyCode.INDETERMINATE, however valid the VAID is. That is
    # deliberate: a copy-pasted revocation seam with nothing wired to it
    # must fail closed, never read as "nothing is revoked". See "Configuring
    # real revocation" below for what plugging in an actual store takes.
    revocation=InMemoryRevocationList(),
    requested_action="data.acme.orders",
    now=datetime.now(timezone.utc),
)

if result.allowed:
    ...  # proceed
else:
    ...  # result.code (a VerifyCode) and result.reason explain why

Valid requests are refused as indeterminate until a real RevocationCheck is configured. This is not a bug to work around; it is the fail-closed default R.4.5 requires. The copy-paste path above stays closed on purpose, so a deployment cannot go live by accident with revocation silently assumed clean.

See examples/guard.py for use as a guard in front of a request handler.

Configuring real revocation

Implement vaid_mint.revocation.RevocationCheck against whatever actually tracks revoked VAIDs in your deployment (a database table, a feed, a cache in front of either) and pass an instance of it instead of InMemoryRevocationList():

from vaid_mint.revocation import RevocationCheck, RevocationStatus

class DatabaseRevocationCheck:
    """Minimal real RevocationCheck: ask the database once per lineage."""

    def __init__(self, db):
        self._db = db

    def check_lineage(self, lineage: list[str]) -> RevocationStatus:
        try:
            revoked_ids = self._db.revoked_vaid_ids(lineage)
        except Exception:
            return RevocationStatus.UNAVAILABLE  # fail closed, not NOT_REVOKED
        if revoked_ids:
            return RevocationStatus.REVOKED
        return RevocationStatus.NOT_REVOKED

result = verify_a2a_message(
    message,
    trust_config=trust_config,
    revocation=DatabaseRevocationCheck(db),
    requested_action="data.acme.orders",
    now=datetime.now(timezone.utc),
)

check_lineage receives the full ordered lineage (root first, leaf last) that verify_a2a_message already assembled; it is never asked to resolve ancestry itself (R.4.1). RevocationStatus is three-valued on purpose: a database error or an unreachable feed must report UNAVAILABLE, never NOT_REVOKED.

Local demo only, never production

InMemoryRevocationList.assume_nothing_revoked() exists for local demos and tests that have no revocation source to wire up and want to exercise the rest of the check (signature, expiry, chain, scope) without every call denying on revocation. It is never appropriate outside a local demo: it makes every lineage read as not-revoked, unconditionally, regardless of whether anything actually was.

# LOCAL DEMO ONLY, NEVER PRODUCTION.
from vaid_mint.revocation import InMemoryRevocationList

demo_result = verify_a2a_message(
    message,
    trust_config=trust_config,
    revocation=InMemoryRevocationList.assume_nothing_revoked(),
    requested_action="data.acme.orders",
    now=datetime.now(timezone.utc),
)

What this checks, and in what order

Exactly the order docs/a2a/v1/extension.md §5 specifies — signature, leaf expiry, chain integrity, per-hop attenuation, requested-action scope, revocation — fail closed at the first failing step. See the module docstring in vaid_a2a/verifier.py for the full reasoning behind the order; it mirrors vaid_mint.verify and vaid_mint.chain, which this package delegates to rather than reimplements.

step delegates to
1. signature / authenticity vaid_mint.verify.verify_vaid_authenticity_graded
2. leaf expiry vaid_mint.document.is_expired_at
3. chain integrity vaid_mint.chain.verify_chain_at
4. per-hop attenuation vaid_mint.chain.verify_chain_at (same call)
5. requested action in leaf scope vaid_mint.document.is_in_scope
6. revocation over the full lineage the caller's own vaid_mint.revocation.RevocationCheck

Test vectors

tests/test_vectors.py runs every vector in docs/a2a/v1/vectors/*.json and asserts the expected result. Regenerate the vectors from vaid-mint tooling with docs/a2a/v1/vectors/generate_vectors.py; see that directory's own README.

Packaged conformance firewall

The vectors above also ship inside the wheel, so an installed copy can be checked without this repository:

vaid-a2a-conformance           # exit 0 = PASS, 1 = BLOCKER
python -m vaid_a2a.conformance  # same

It replays all 12 extension vectors through verify_a2a_message and asserts the (result, error_code) pair docs/a2a/v1/extension.md section 5 specifies for each, at the verification instant each vector names rather than the wall clock. It pins verdicts, not bytes: this package signs and canonicalizes nothing of its own, and the bytes beneath it are proven by vaid-mint-conformance. The bundled vectors are byte-identical to docs/a2a/v1/vectors/, which CI checks by comparing the file listings and then every file.

Non-goals

  • Does not depend on a2a-python or any A2A SDK — Message is accepted as a plain dict, so this works against any A2A server or client.
  • Does not mint VAIDs (vaid_mint.mint.MintService) or implement revocation transport (docs/spec/revocation.md R.1) — both out of scope here, as they are for the extension itself.
  • Does not implement actorChain (#2028) or require it to be present; see the spec's §4.1 for how the two compose when both are active.

Metadata

Release files for vaid-a2a 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for vaid-a2a 0.2.1
File Size Uploaded Provenance
vaid_a2a-0.2.1.tar.gz 37.3 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for vaid-a2a 0.2.1
File Interpreter ABI Platform
vaid_a2a-0.2.1-py3-none-any.whl Python 3 none any Details

Total release size: 77.7 kB

Release files / vaid_a2a-0.2.1.tar.gz

Download URL vaid_a2a-0.2.1.tar.gz
Size 37.3 kB
Tags Source
SHA-256 checksum
How to use checksums
c0c5239d0b108b99ca41f2668b1aade83d28d3f56855bcdf479edd7a3d586bf8
BLAKE2b-256 checksum
How to use checksums
d2e468f75c25dc60f70e963597b179425b24303837d1b310cfbee2586e5ade60
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.12.15

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

Release files / vaid_a2a-0.2.1-py3-none-any.whl

Download URL vaid_a2a-0.2.1-py3-none-any.whl
Size 40.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
77dfc3b17521e8e13f3bb771a40b1c14a229cb4b45c3bf367e624d9b30ec79d6
BLAKE2b-256 checksum
How to use checksums
ac05c6edcebd8c0084f35cb2c120782ca4230783e4dc2fbafe06ba177191c38a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.12.15

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

Security for vaid-a2a 0.2.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for vaid_a2a-0.2.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page