Skip to content

attenu-guard 0.6.0

Choose a tag to compare

@rafaelasor rafaelasor released this 29 Aug 04:34
· 99 commits to main since this release

Added

  • The interop test vectors ship inside the package as attenu_guard.vectors
    (VECTOR_NAMES, load_vector, load_vectors, read_vector_bytes, read through
    importlib.resources). The Internet-Draft promises a chain that MUST verify and six that
    MUST each be rejected for a named reason, so that an implementation written in ANY language
    from the draft alone can score its own offline verifier; shipping them means doing that needs
    pip install attenu-guard and no clone. tests/vectors/generate.py is the single writer for
    both copies — it serialises each vector once and writes those bytes to tests/vectors/ and
    src/attenu_guard/vectors/ — and tests/test_wire.py asserts the two are byte-identical, so
    they cannot diverge. A CI step verifies they survive an install, not just a checkout.
  • A2A adapter (attenu_guard.adapters.a2a, extra a2a, tested against a2a-sdk 1.1.2): carries the attenuated
    delegation chain across an Agent2Agent hop, so a remote agent in another process runs with permissions bounded by the
    calling agent's. Two halves on public seams — client side, a DelegationInterceptor (ClientCallInterceptor.before)
    mints the child with parent.delegate(...) and puts the signed Delegation Chain (attenu_guard.wire) on the outgoing
    message as an A2A extension (Message.extensions + Message.metadata[<uri>], spec §4.6.2, with the
    A2A-Extensions header §4.6.1); server side, GuardedAgentExecutor wraps the deployment's AgentExecutor.execute,
    verifies the chain offline (wire.load: signatures, parent-hash linkage, depth, child ⊆ parent at every hop, expiry)
    and mints the served Guard from the verified leaf, narrowed again by what the remote task needs. A missing, forged,
    spliced, widened or expired chain — or any exception raised while deciding — refuses the request before the remote
    agent's own logic starts, returning the denial contract in the extension's metadata slot. guarded_tool(fn, scope=…)
    checks before each tool body; require_guard() refuses a tool reached outside the executor. verify_hop(tokens, signer, client_bundle=…, server_bundle=…) checks the caller's ledger, the remote ledger and the tokens that bind them
    from those inputs alone, and reports an unsupplied bundle as "not checked" rather than as passing. This answers A2A
    §7.6.4, which states that the protocol defines no scope, validity or revocation semantics for an in-task authorization
    decision. Cross-process revocation propagation remains open: an expired chain is refused and revocation_check= is the
    seam for a status list, both documented as limits. Example (offline demo plus a live_smoke.py verified over a real
    Starlette/uvicorn HTTP hop) and 35 offline tests; seventeenth entry in docs/INTEGRATIONS.md.