Guarantees & evidence

A protocol that sells auditability should be auditable. This table maps every guarantee we make to the function that enforces it and the published test that proves it.

Verify without repo access: every referenced source file and test ships inside the MIT-licensed npm tarballs (npm pack @aroha-sdk/credentials, @aroha-sdk/delegation, @aroha-sdk/mcp-guard — then read src/). Anchors are file + function, not line numbers, so they survive refactors. Server-side guarantees cite the public spec instead of private code — we mark the difference rather than blur it.

Authority can only narrow

A delegated spending limit can never exceed its parent's

Enforced at

@aroha-sdk/credentials · mandate.ts · assertNarrowing()

Proven by

mandate.test.ts · "rejects spendLimitUsd widening" (and the cart variant)

A delegated task scope is a strict subset of the parent's

Enforced at

@aroha-sdk/credentials · mandate.ts · assertTaskNarrowing()

Proven by

chain.test.ts · "rejects a scope widened mid-chain"

A blocked capability stays blocked all the way down a chain

Enforced at

@aroha-sdk/delegation · chain.ts · verifyMandateChain()

Proven by

chain.test.ts · "rejects a dropped blocked capability"

N children cannot multiply a parent budget (no double-allocation)

Enforced at

@aroha-sdk/credentials · spending-ledger.ts · SpendingLedger.allocate() → BudgetExhaustedError

Proven by

mandate.test.ts · "rejects allocation that would exceed parent limit"

Tokens cannot be forged, replayed, or redirected

Tampering with any field breaks the token

Enforced at

@aroha-sdk/credentials · mandate.ts · verifyMandate() / verifyTaskMandate() — Ed25519 over canonicalised JSON

Proven by

chain.test.ts · "rejects a tampered token"

A mandate is bound to exactly one grantee

Enforced at

@aroha-sdk/credentials · mandate.ts · verifyTaskMandate(expectedGrantee)

Proven by

mandate.test.ts · "rejects grantee mismatch"

Mandates expire; a stale grant is a dead grant

Enforced at

@aroha-sdk/credentials · mandate.ts · expiry check in both verifiers; chain-level containment in verifyMandateChain()

Proven by

mandate.test.ts and chain.test.ts · "rejects an expired mandate"

A rotated-out key cannot sign, and rotation cannot be replayed

Enforced at

Registry (server-side): nonce consumed atomically, 5-minute freshness window, self-signed by the outgoing key — see the public DID method spec, Update operation

Proven by

Specified in aroha-spec · spec/did-method.md · §Update; conformance test-matrix

Chains of custody cannot be spliced

Each link's grantor must be the previous link's grantee, with parentMandateId continuity

Enforced at

@aroha-sdk/delegation · chain.ts · verifyMandateChain()

Proven by

chain.test.ts · "rejects broken grantor→grantee custody"

A hop cannot be impersonated — every link's signature is checked against its signer's resolved key

Enforced at

@aroha-sdk/delegation · chain.ts · verifyMandateChain() + resolvePublicKey

Proven by

chain.test.ts · "rejects a child signed by the wrong key (impersonated hop)"

An untrusted root issuer is rejected at the door

Enforced at

@aroha-sdk/delegation · serve-delegated.ts · verifyRequest() — trust-anchor pinning

Proven by

examples/delegation-web/demo.mjs · scenario 3 (forged root → HTTP 400)

Recursion has a floor

maxDelegationDepth strictly decrements; a depth-0 mandate is a dead end

Enforced at

@aroha-sdk/delegation · chain.ts (chain rule) + serve-delegated.ts · delegate() (refuses before signing)

Proven by

chain.test.ts · "rejects any child under maxDelegationDepth=0", "rejects a child that fails to decrement depth", "rejects a child that omits depth when the parent bounds it"

The tool plane fails closed

Block beats limit beats gate — precedence is fixed, and a zero limit denies immediately

Enforced at

@aroha-sdk/mcp-guard · engine.ts · GuardEngine.decide()

Proven by

engine.test.ts · "applies precedence block > limit > gate", "a zero limit denies immediately"

An unanswered or unsupported approval prompt is a denial, never an allowance

Enforced at

@aroha-sdk/mcp-guard · proxy.ts · requestApproval() (timeout → settle(false)) and resolveGate() (client error → resolve(false))

Proven by

Deny paths implemented as the only defaults in proxy.ts; rule engine covered by the 17-test engine suite

Run the proofs yourself

The delegation demo exercises the chain guarantees live — three agents on localhost, including a forged root, a widened scope, and a blown depth limit, all rejected:

npm i @aroha-sdk/delegation @aroha-sdk/credentials @noble/ed25519
node examples/delegation-web/demo.mjs   # ships in the delegation package docs