Skip to content

attenu-guard 0.9.0

Choose a tag to compare

@github-actions github-actions released this 31 Aug 06:04
· 64 commits to main since this release

Fixed

  • Integers beyond the RFC 8785 safe range (±(2**53-1)) are now rejected — at canonicalization, at
    RowLimit/SpendCap/CallLimit construction, and by wire.load (as malformed) — instead of
    silently colliding with a neighbouring integer once rendered through binary64. A tenth reject
    vector, reject_unsafe_integer.json, brings the interop suite to 20.
  • evidence.verify_bundle and AuditLog.verify_anchor now check the bundle/anchor schema version
    and chain identity instead of ignoring them, so a bundle for the wrong version or the wrong chain
    no longer verifies.

Added

  • AuditLog.append now raises CommittedAuditError (carrying the committed entry) if persisting
    an entry fails after it was already committed to the in-memory chain — the file write or a sink
    raising no longer looks the same as nothing having been recorded. The entry stays committed;
    callers must not retry the call that produced it on the strength of this error alone.
  • Execution binding, opt-in per chain via Guard.issue(..., schema_version=2) (schema version 1
    is unchanged and remains the default): check()/record_denial() now allocate a call_id
    (fail-closed, with meters restored, if the CSPRNG fails) and return it on Decision.call_id;
    check() gains authorized_params/capture/adapter and refuses further calls once the node
    is complete()d (ReasonCode.NODE_FINALIZED). Guard.record_outcome(call_id, body_state, ...)
    binds what a body-owning wrapper observed afterwards — returned/raised/abandoned/deferred,
    with error_code required exactly when raised. On a schema_version=2 chain, complete()
    returns a bool-coercible CompletionResult and refuses while calls are pending; on
    schema_version=1 it still returns a plain bool, byte-and-type identical to every release
    before 0.9.0. revoke()/revoke_agent() snapshot still-pending call_ids onto the kill entry
    as pending_at_kill — atomically, under one hold of the chain lock, together with the
    revocation itself and (in check()) with complete()'s own check-pending-then-append sequence
    — without clearing them, so a late record_outcome() after a kill is still accepted. Every
    pre-commit check()/record_outcome() failure (not only CSPRNG exhaustion) rolls back its
    meters/bookkeeping. Arguments are committed via params_c14n_v1 (attenu_guard.params):
    SHA-256(raw_salt || JCS(params)), never the raw value — closing, for this profile only, the
    one gap the shared JCS canonicalizer leaves open for out-of-range integral floats, without
    changing that canonicalizer's own behaviour elsewhere. evidence.verify_bundle gains
    execution_binding: per-call observed/unobserved/unaccounted (an outcome counts as observed
    only once it is bound correctly — right node, right order), per-node
    finalized/in_progress/revoked_with_pending, an aggregate clean/incomplete/failed, and
    params_coverage (computed over every call, not only those with an outcome) as its own axis —
    not applicable for a schema-version-1 bundle. verify_bundle also rejects a rootless bundle
    and accepts an optional independently retained expected_anchor/expected_head, so a rewritten
    bundle whose own (self-consistent) anchor cannot be relied on is still caught. The LangGraph
    adapter (adapters.langgraph) is the reference wiring: guard_node/DelegatedToolNode call
    record_outcome on a schema_version=2 guard, sync and async, from an immutable
    pre-invocation argument snapshot (a callable that mutates its own inputs cannot cause a false
    params mismatch), with generators/futures reported deferred and asyncio.CancelledError
    reported abandoned. Schema and verifier are event- and version-aware and strict: a v2 allow
    REQUIRES capture/adapter (Guard.check() supplies pre_hook_only plus a guard-attributed
    adapter when the caller passes neither — a bare check() IS itself pre_hook_only observation,
    never merely absent), deny FORBIDS every allow-only field, and a v1 entry FORBIDS every
    v2-only field (including call_id — v1 never allocates one); tests/test_execution_binding.py
    runs in CI. A language-neutral params_c14n_v1 parity vector file
    (tests/vectors/params_c14n/params_c14n_v1.json, consumed by tests/test_params_c14n_vectors.py)
    covers its accepted/rejected numeric boundaries and salt handling; the TypeScript consumer of
    this same file is being built on attenu-guard-ts (feat/090-execution-binding) — parity
    between the two is a release gate for 0.9.0, not deferred work.

Changed

  • Behaviour change: constructing an AuditLog (or Guard.issue) with a path/audit_path that
    already names a non-empty file now raises FileExistsError instead of silently truncating it.
    Pass overwrite=True (Guard.issue(..., audit_overwrite=True)) to keep the old reset-on-open
    behaviour where that is what you want.