From 2b18edf352ba7c382a691ce12e979ed8ca5663d4 Mon Sep 17 00:00:00 2001 From: luisllaver <94252605+luisllaver@users.noreply.github.com> Date: Wed, 20 May 2026 13:41:33 -0300 Subject: [PATCH 01/32] Add opt-in AURA trust-check adapter (integrations/aura) --- integrations/aura/adapter.test.ts | 133 ++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 integrations/aura/adapter.test.ts diff --git a/integrations/aura/adapter.test.ts b/integrations/aura/adapter.test.ts new file mode 100644 index 0000000..3725ea5 --- /dev/null +++ b/integrations/aura/adapter.test.ts @@ -0,0 +1,133 @@ +/** + * Offline tests for the AURA trust-check adapter (vitest). + * + * No network: every call replays a recorded /check body via the `fetchImpl` + * injection seam. Run with `vitest run`. + * + * Coverage: one assertion per verdict class, the beforeSettle gate + * (allow-list pass/reject, custom allow, failOpen), the network-failure path + * (fail-closed by default), and input validation. + */ + +import { describe, it, expect } from 'vitest'; +import { + auraVerdict, + beforeSettle, + AuraUntrusted, + type FetchLike, +} from './adapter.js'; + +// ── recorded /check bodies, one per verdict class ──────────────────────────── +const RECORDED: Record> = { + 'did:aura:trusted-bot': { + did: 'did:aura:trusted-bot', verdict: 'trusted', + reason: 'strong on-chain track record (composite 0.86)', + has_history: true, score: 0.86, interactions: 142, + dimensions: { financial_integrity: 0.95, task_completion: 0.92 }, + }, + 'did:aura:caution-bot': { + did: 'did:aura:caution-bot', verdict: 'caution', + reason: 'mixed history (composite 0.55)', has_history: true, score: 0.55, + }, + 'did:aura:risky-bot': { + did: 'did:aura:risky-bot', verdict: 'high_risk', + reason: 'poor track record (composite 0.22)', has_history: true, score: 0.22, + dimensions: { financial_integrity: 0.12 }, + }, + 'did:aura:fresh-bot': { + did: 'did:aura:fresh-bot', verdict: 'new', + reason: 'registered identity, no interactions yet', has_history: false, score: null, + }, + 'did:aura:ghost-bot': { + did: 'did:aura:ghost-bot', verdict: 'unknown', + reason: 'no track record — unverified counterparty', has_history: false, score: null, + }, +}; + +const okFetch: FetchLike = async (url) => { + const did = new URL(url).searchParams.get('did') ?? ''; + const body = RECORDED[did] ?? RECORDED['did:aura:ghost-bot']; + return { ok: true, status: 200, json: async () => body }; +}; + +const failFetch: FetchLike = async () => { + throw new Error('connection refused'); +}; + +// ── verdict classes ────────────────────────────────────────────────────────── +describe('verdict classes', () => { + const cases: [string, string, boolean][] = [ + ['did:aura:trusted-bot', 'trusted', true], + ['did:aura:caution-bot', 'caution', true], + ['did:aura:risky-bot', 'high_risk', false], + ['did:aura:fresh-bot', 'new', false], + ['did:aura:ghost-bot', 'unknown', false], + ]; + it.each(cases)('%s -> %s', async (did, expected, ok) => { + const v = await auraVerdict(did, { fetchImpl: okFetch }); + expect(v.verdict).toBe(expected); + expect(v.ok).toBe(ok); + expect(v.did).toBe(did); + expect(v.reason.length).toBeGreaterThan(0); + }); + + it('exposes dimensions for agents with history', async () => { + const v = await auraVerdict('did:aura:risky-bot', { fetchImpl: okFetch }); + expect(v.hasHistory).toBe(true); + expect(v.dimensions?.financial_integrity).toBe(0.12); + }); + + it('new agent has null score', async () => { + const v = await auraVerdict('did:aura:fresh-bot', { fetchImpl: okFetch }); + expect(v.score).toBeNull(); + expect(v.hasHistory).toBe(false); + }); +}); + +// ── the beforeSettle gate ───────────────────────────────────────────────────── +describe('beforeSettle gate', () => { + it('allows trusted / caution / new by default', async () => { + expect((await beforeSettle('did:aura:trusted-bot', { fetchImpl: okFetch })).verdict).toBe('trusted'); + expect((await beforeSettle('did:aura:caution-bot', { fetchImpl: okFetch })).verdict).toBe('caution'); + expect((await beforeSettle('did:aura:fresh-bot', { fetchImpl: okFetch })).verdict).toBe('new'); + }); + + it('rejects high_risk', async () => { + await expect(beforeSettle('did:aura:risky-bot', { fetchImpl: okFetch })).rejects.toBeInstanceOf(AuraUntrusted); + }); + + it('rejects unknown by default', async () => { + await expect(beforeSettle('did:aura:ghost-bot', { fetchImpl: okFetch })).rejects.toBeInstanceOf(AuraUntrusted); + }); + + it('strict allow rejects new', async () => { + await expect( + beforeSettle('did:aura:fresh-bot', { allow: ['trusted', 'caution'], fetchImpl: okFetch }), + ).rejects.toBeInstanceOf(AuraUntrusted); + }); +}); + +// ── network-failure path ─────────────────────────────────────────────────────── +describe('network failure', () => { + it('auraVerdict returns unknown, does not throw', async () => { + const v = await auraVerdict('did:aura:trusted-bot', { fetchImpl: failFetch }); + expect(v.verdict).toBe('unknown'); + expect(v.reason.toLowerCase()).toContain('unreachable'); + }); + + it('gate is fail-closed by default on unreachable', async () => { + await expect(beforeSettle('did:aura:trusted-bot', { fetchImpl: failFetch })).rejects.toBeInstanceOf(AuraUntrusted); + }); + + it('gate passes on unreachable when failOpen', async () => { + const v = await beforeSettle('did:aura:trusted-bot', { failOpen: true, fetchImpl: failFetch }); + expect(v.verdict).toBe('unknown'); + }); +}); + +// ── input validation ──────────────────────────────────────────────────────────── +describe('input validation', () => { + it.each(['', 'not-a-did', 'z6Mk-no-prefix'])('rejects bad DID %s', async (bad) => { + await expect(auraVerdict(bad, { fetchImpl: okFetch })).rejects.toThrow(); + }); +}); From 6a77dd18f87b22a4102f33e3606a644a61b08ea1 Mon Sep 17 00:00:00 2001 From: luisllaver <94252605+luisllaver@users.noreply.github.com> Date: Wed, 20 May 2026 13:41:35 -0300 Subject: [PATCH 02/32] Add opt-in AURA trust-check adapter (integrations/aura) --- integrations/aura/adapter.ts | 155 +++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 integrations/aura/adapter.ts diff --git a/integrations/aura/adapter.ts b/integrations/aura/adapter.ts new file mode 100644 index 0000000..6e52d27 --- /dev/null +++ b/integrations/aura/adapter.ts @@ -0,0 +1,155 @@ +/** + * AURA trust-check adapter — a zero-dependency, read-only reputation lookup. + * + * Drop this folder into any agent/payment project to gate a settlement behind + * a backward-looking trust verdict for the *counterparty* agent. It does NOT + * sign, hold keys, move funds, or touch your wallet. It makes one HTTPS GET + * and returns a verdict. + * + * Design boundary (intentional): + * - read-only: the only network call is GET /check?did=... + * - no auth: /check is a public endpoint; no API key, no secret + * - no coupling: uses global fetch (Node 18+ / ESM). No third-party imports. + * - fail-closed: on network failure the verdict is `unknown`, and the default + * gate (beforeSettle) rejects `unknown` — an unreachable AURA + * never silently waves a counterparty through. Set + * `failOpen: true` to invert that. + */ + +export const DEFAULT_BASE_URL = 'https://agent.auraopenprotocol.org'; +export const DEFAULT_TIMEOUT_MS = 8000; + +/** Verdicts safe to proceed with by default — rejects high_risk + unknown. */ +export const DEFAULT_ALLOW = ['trusted', 'caution', 'new'] as const; + +export type Verdict = 'trusted' | 'caution' | 'high_risk' | 'new' | 'unknown'; +const VERDICTS: readonly Verdict[] = ['trusted', 'caution', 'high_risk', 'new', 'unknown']; + +export interface AuraVerdict { + /** the DID that was checked */ + did: string; + /** trusted | caution | high_risk | new | unknown */ + verdict: Verdict; + /** human-readable explanation */ + reason: string; + /** composite 0..1, or null when there is no history */ + score: number | null; + /** True for verdicts safe to proceed with (trusted / caution) */ + ok: boolean; + /** True once the agent has on-chain interactions */ + hasHistory: boolean; + /** per-dimension breakdown (which axis is weak), or null */ + dimensions: Record | null; + /** the untouched JSON body */ + raw: Record; +} + +/** Thrown by beforeSettle() when a counterparty fails the trust gate. */ +export class AuraUntrusted extends Error { + readonly verdict: AuraVerdict; + constructor(verdict: AuraVerdict) { + super(`trust gate rejected ${verdict.did}: ${verdict.verdict} — ${verdict.reason}`); + this.name = 'AuraUntrusted'; + this.verdict = verdict; + } +} + +/** Injection seam: a fetch-compatible function. Defaults to global fetch. */ +export type FetchLike = (url: string, init?: { signal?: AbortSignal }) => Promise<{ + ok: boolean; + status: number; + json: () => Promise; +}>; + +export interface VerdictOptions { + baseUrl?: string; + timeoutMs?: number; + /** Override for tests; production callers ignore it. */ + fetchImpl?: FetchLike; +} + +export interface GateOptions extends VerdictOptions { + allow?: readonly string[]; + /** Treat an unreachable AURA as a pass. Off by default. */ + failOpen?: boolean; +} + +function toVerdict(did: string, body: Record): AuraVerdict { + let verdict = String(body.verdict ?? 'unknown') as Verdict; + if (!VERDICTS.includes(verdict)) verdict = 'unknown'; + return { + did: typeof body.did === 'string' ? body.did : did, + verdict, + reason: String(body.reason ?? ''), + score: typeof body.score === 'number' ? body.score : null, + ok: verdict === 'trusted' || verdict === 'caution', + hasHistory: Boolean(body.has_history), + dimensions: (body.dimensions as Record | null) ?? null, + raw: body, + }; +} + +function unreachable(did: string, reason: string): AuraVerdict { + return { did, verdict: 'unknown', reason, score: null, ok: false, hasHistory: false, dimensions: null, raw: {} }; +} + +/** + * Look up the trust verdict for a counterparty DID. Never rejects on a + * network/parse failure — resolves to an `unknown` verdict instead, leaving + * the proceed/abort decision to the caller's policy (see beforeSettle). + * + * const v = await auraVerdict('did:aura:z6Mk...'); + * console.log(v.verdict, v.reason, v.score); + */ +export async function auraVerdict(did: string, opts: VerdictOptions = {}): Promise { + if (!did || !did.startsWith('did:')) { + throw new Error(`invalid DID: ${JSON.stringify(did)} (must start with 'did:')`); + } + const baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, ''); + const url = `${baseUrl}/check?did=${encodeURIComponent(did)}`; + const doFetch = opts.fetchImpl ?? (globalThis.fetch as unknown as FetchLike); + + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? DEFAULT_TIMEOUT_MS); + try { + const resp = await doFetch(url, { signal: controller.signal }); + if (!resp.ok) return unreachable(did, `AURA returned HTTP ${resp.status}`); + const body = (await resp.json()) as Record; + if (typeof body !== 'object' || body === null) { + return unreachable(did, 'AURA returned an unexpected shape'); + } + return toVerdict(did, body); + } catch (e) { + return unreachable(did, `AURA unreachable: ${(e as Error).message}`); + } finally { + clearTimeout(timer); + } +} + +/** + * Gate a settlement behind a trust check. Resolves with the verdict on pass, + * throws AuraUntrusted on fail. + * + * try { + * await beforeSettle(counterpartyDid); // rejects high_risk + unknown + * await client.pay({ resource, amount, payTo }); + * } catch (e) { + * if (e instanceof AuraUntrusted) abort(e.message); + * } + * + * Tighten to reject brand-new agents too: + * await beforeSettle(did, { allow: ['trusted', 'caution'] }); + * + * failOpen: true makes an *unreachable* AURA pass through. Off by default — + * absence of evidence is not evidence of trust. + */ +export async function beforeSettle(did: string, opts: GateOptions = {}): Promise { + const allow = opts.allow ?? DEFAULT_ALLOW; + const v = await auraVerdict(did, opts); + if (allow.includes(v.verdict)) return v; + if (opts.failOpen && v.verdict === 'unknown' && !v.hasHistory) return v; + throw new AuraUntrusted(v); +} + +/** Alias — same gate, name that reads better at non-payment call sites. */ +export const requireTrust = beforeSettle; From 3e5db35b4ffb566bfb121f5d69df6d93aa72a2ba Mon Sep 17 00:00:00 2001 From: luisllaver <94252605+luisllaver@users.noreply.github.com> Date: Wed, 20 May 2026 13:41:36 -0300 Subject: [PATCH 03/32] Add opt-in AURA trust-check adapter (integrations/aura) --- integrations/aura/index.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 integrations/aura/index.ts diff --git a/integrations/aura/index.ts b/integrations/aura/index.ts new file mode 100644 index 0000000..a622e23 --- /dev/null +++ b/integrations/aura/index.ts @@ -0,0 +1,25 @@ +/** + * AURA trust-check adapter — opt-in, read-only counterparty reputation. + * + * import { beforeSettle, AuraUntrusted } from './integrations/aura'; + * + * try { + * await beforeSettle(counterpartyDid); + * await client.pay({ resource, amount, payTo }); + * } catch (e) { + * if (e instanceof AuraUntrusted) abort(e.message); + * } + * + * Zero dependencies (global fetch). Does not sign, hold keys, or move funds. + * See README.md for the enable section and THREAT_MODEL.md for the boundary. + */ +export { + auraVerdict, + beforeSettle, + requireTrust, + AuraUntrusted, + DEFAULT_BASE_URL, + DEFAULT_ALLOW, +} from './adapter.js'; + +export type { AuraVerdict, Verdict, VerdictOptions, GateOptions, FetchLike } from './adapter.js'; From d034a676f3c9acc63ed5b215cb39ef409c67f87e Mon Sep 17 00:00:00 2001 From: luisllaver <94252605+luisllaver@users.noreply.github.com> Date: Wed, 20 May 2026 13:41:37 -0300 Subject: [PATCH 04/32] Add opt-in AURA trust-check adapter (integrations/aura) --- .../vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 integrations/aura/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json diff --git a/integrations/aura/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json b/integrations/aura/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json new file mode 100644 index 0000000..66826c0 --- /dev/null +++ b/integrations/aura/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json @@ -0,0 +1 @@ +{"version":"4.1.5","results":[[":adapter.test.ts",{"duration":9.982899999999972,"failed":false}]]} \ No newline at end of file From 912ed215cfa1ad012845cd770af961ae1535d583 Mon Sep 17 00:00:00 2001 From: luisllaver <94252605+luisllaver@users.noreply.github.com> Date: Wed, 20 May 2026 13:41:38 -0300 Subject: [PATCH 05/32] Add opt-in AURA trust-check adapter (integrations/aura) --- integrations/aura/README.md | 108 ++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 integrations/aura/README.md diff --git a/integrations/aura/README.md b/integrations/aura/README.md new file mode 100644 index 0000000..dba5d29 --- /dev/null +++ b/integrations/aura/README.md @@ -0,0 +1,108 @@ +# AURA trust-check adapter (TypeScript) + +Opt-in, **read-only** counterparty reputation for PayBot. One HTTPS GET +answers *"can I trust this agent before I settle a payment to it?"* — a +natural `beforeSettle` gate in front of `client.pay()`. + +- **Zero dependencies** — global `fetch` (Node 18+), no new packages. +- **Read-only** — the only network call is `GET /check?did=...`. No auth, no key. +- **No coupling** — does not sign, hold keys, move USDC, or touch the wallet. + PayBot's `pay()` flow is untouched; this sits *in front* of it. +- **Off by default** — nothing runs until you call it. + +## Enable (opt-in) + +Gate the settlement at the call site. No global hooks, no monkey-patching: + +```ts +import { PayBotClient } from 'paybot-sdk'; +import { beforeSettle, AuraUntrusted } from './integrations/aura'; + +const client = new PayBotClient(config); + +async function payChecked(counterpartyDid: string, req: PaymentRequest) { + try { + await beforeSettle(counterpartyDid); // rejects high_risk + unknown + } catch (e) { + if (e instanceof AuraUntrusted) { + console.warn('blocked:', e.message); // your policy decides + return; + } + throw e; + } + return client.pay(req); // existing flow, unchanged +} +``` + +`payTo` in a `PaymentRequest` is a wallet address; the AURA DID is the +counterparty's portable identity, supplied by your own mapping. The gate keys +on that DID — it composes cleanly with PayBot's existing `TRUST_VIOLATION` +error model as a *pre-flight* reputation axis. + +Prefer to read the verdict instead of throwing? + +```ts +import { auraVerdict } from './integrations/aura'; + +const v = await auraVerdict(counterpartyDid); +console.log(v.verdict); // trusted | caution | high_risk | new | unknown +console.log(v.reason, v.score, v.ok); + +// v.dimensions tells you *which* axis is weak, not just the aggregate: +if ((v.dimensions?.financial_integrity ?? 1) < 0.4) requireManualReview(); +``` + +## Verdicts + +| verdict | meaning | `ok` | +|---|---|---| +| `trusted` | strong on-chain track record (composite ≥ 0.70) | ✅ | +| `caution` | mixed history (0.40–0.70) | ✅ | +| `high_risk` | poor track record (< 0.40) | ❌ | +| `new` | registered identity, no interactions yet | ❌ | +| `unknown` | no track record — or AURA was unreachable | ❌ | + +## Policy knobs + +```ts +await beforeSettle(did, { allow: ['trusted', 'caution'] }); // reject new too +await beforeSettle(did, { failOpen: true }); // unreachable => pass +await beforeSettle(did, { baseUrl: 'https://my-mirror', timeoutMs: 5000 }); +``` + +`requireTrust` is an alias of `beforeSettle` for non-payment call sites. + +## Failure behavior + +`auraVerdict()` **never rejects on a network error** — it resolves to an +`unknown` verdict with the reason set. The gate then decides: + +- **default (`failOpen: false`)** — `unknown` rejected → unreachable AURA + blocks the settlement. *Fail-closed.* +- **`failOpen: true`** — `unknown` from an unreachable endpoint passes, so AURA + can never take your payment flow down. *Fail-open.* + +The signal is **purely additive**: remove the adapter or take AURA down, and +PayBot behaves exactly as before. + +## Tests + +Offline — every call replays a recorded `/check` body via the `fetchImpl` +injection seam: + +```bash +npx vitest run --config integrations/aura/vitest.config.ts +``` + +17 tests: all five verdict classes, the gate's allow-list + `failOpen`, the +unreachable path, and input validation. + +## Boundary & threats + +See [THREAT_MODEL.md](./THREAT_MODEL.md) — what the verdict does and does not +prove, and the failure modes a verifier should account for. + +## What's behind the verdict + +[AURA Open Protocol](https://auraopenprotocol.org) — W3C DID identity plus 8 +on-chain reputation dimensions on Base L2. Docs: https://dev.auraopenprotocol.org From 6f2106031328f75826c7e59414d7e88b2c0f7ecf Mon Sep 17 00:00:00 2001 From: luisllaver <94252605+luisllaver@users.noreply.github.com> Date: Wed, 20 May 2026 13:41:39 -0300 Subject: [PATCH 06/32] Add opt-in AURA trust-check adapter (integrations/aura) --- integrations/aura/THREAT_MODEL.md | 52 +++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 integrations/aura/THREAT_MODEL.md diff --git a/integrations/aura/THREAT_MODEL.md b/integrations/aura/THREAT_MODEL.md new file mode 100644 index 0000000..286a3b1 --- /dev/null +++ b/integrations/aura/THREAT_MODEL.md @@ -0,0 +1,52 @@ +# Threat model — AURA trust-check adapter + +A short, honest boundary statement. The verdict is **one backward-looking +signal**, not a security guarantee. Read this before treating `trusted` as a +green light for an irreversible settlement. + +## What the verdict proves + +- The DID has (or lacks) an on-chain interaction history on AURA, summarized + into a composite score and per-dimension breakdown. +- It is **backward-looking**: a statement about past recorded behavior, not a + prediction or an authorization for the *current* payment. + +## What it explicitly does NOT prove + +- **Not payment-safety.** A `trusted` agent can still be the wrong recipient or + request a bad amount. Keep this separate from PayBot's own checks + (`TRUST_VIOLATION`, limits) so the settlement decision stays auditable. +- **Not execution quality.** It says nothing about whether *this* settle succeeds. +- **Not identity proof of the live caller.** It checks a DID's reputation, not + that the entity you're paying controls that DID (see "Spoofed DID"). + +## Failure modes a caller must account for + +| # | Threat | Mitigation in this adapter | Residual risk owned by caller | +|---|---|---|---| +| 1 | **Endpoint unreachable / timeout** | Resolves to `unknown` (never rejects). Gate is fail-closed by default; `AbortController` enforces `timeoutMs`. | Choose `failOpen` deliberately; pick a sane `timeoutMs`. | +| 2 | **Spoofed DID** — recipient claims a DID it doesn't control | Out of scope: adapter checks reputation, not control of the key. | Bind the DID to the `payTo` address / verify control before trusting. | +| 3 | **Stale verdict** — score lags very recent bad behavior | Each call is live (no caching here). | If you cache, bound the TTL; don't reuse a verdict across sessions. | +| 4 | **Endpoint MITM / response tampering** | HTTPS to a pinned host (`agent.auraopenprotocol.org`). Verdict strings validated against a fixed allow-list; unknown values collapse to `unknown`. | Don't point `baseUrl` at an untrusted mirror. | +| 5 | **Score gaming / Sybil** — cheap DIDs farming a `trusted` score | Inherited from AURA's on-chain cost + dispute dimension; not solvable in the adapter. | Weight `dimensions` (e.g. require non-trivial history) for high-value settlements rather than trusting the aggregate alone. | +| 6 | **Over-trust** — using the verdict as sole gate for an irreversible payment | `new`/`unknown` rejected by default; `dimensions` exposed. | Combine with PayBot limits + escrow + manual review for high-value flows. | + +## Data handled + +- **Sent:** only the counterparty DID, as a query parameter to `/check`. No + PII, no payment payload, no secrets, no keys. +- **Stored:** nothing. The adapter is stateless. +- **Received:** the public `/check` JSON body. Surfaced verbatim on `.raw`. + +## Trust boundary summary + +``` +PayBot host --(DID only, HTTPS GET)--> AURA /check --> verdict + | | + | PayBot limits / TRUST_VIOLATION (separate, yours) | + v v + settle decision (auditable, your code) +``` + +The adapter sits on the read-only reputation edge. Signing, USDC movement, and +the final settle decision stay in PayBot, where they can be audited. From 309fd842e30fe5053065795facd36228af738ad2 Mon Sep 17 00:00:00 2001 From: luisllaver <94252605+luisllaver@users.noreply.github.com> Date: Wed, 20 May 2026 13:41:40 -0300 Subject: [PATCH 07/32] Add opt-in AURA trust-check adapter (integrations/aura) --- integrations/aura/vitest.config.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 integrations/aura/vitest.config.ts diff --git a/integrations/aura/vitest.config.ts b/integrations/aura/vitest.config.ts new file mode 100644 index 0000000..d2e4a0d --- /dev/null +++ b/integrations/aura/vitest.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'vitest/config'; + +// Standalone config so the adapter's tests run independently of the monorepo +// (the root config only globs packages/*/src). Used for local verification; +// the target repo runs these under its own vitest setup. +export default defineConfig({ + test: { + include: ['**/*.test.ts'], + environment: 'node', + }, +}); From b876219f40676c119a5efce5a67d693c31f5af87 Mon Sep 17 00:00:00 2001 From: luisllaver <94252605+luisllaver@users.noreply.github.com> Date: Wed, 20 May 2026 13:44:24 -0300 Subject: [PATCH 08/32] Add opt-in AURA trust-check adapter (integrations/aura) From 04d8d667b97ed0fa6a94f9a2c49617b987d261b2 Mon Sep 17 00:00:00 2001 From: luisllaver <94252605+luisllaver@users.noreply.github.com> Date: Wed, 20 May 2026 13:44:25 -0300 Subject: [PATCH 09/32] Add opt-in AURA trust-check adapter (integrations/aura) From 3d3ee3e99806b44030f65859e80326b57d0bc05d Mon Sep 17 00:00:00 2001 From: luisllaver <94252605+luisllaver@users.noreply.github.com> Date: Wed, 20 May 2026 13:44:26 -0300 Subject: [PATCH 10/32] Add opt-in AURA trust-check adapter (integrations/aura) From eb9357af362706a9b4e9abe999a78bf050a6a54b Mon Sep 17 00:00:00 2001 From: luisllaver <94252605+luisllaver@users.noreply.github.com> Date: Wed, 20 May 2026 13:44:27 -0300 Subject: [PATCH 11/32] Add opt-in AURA trust-check adapter (integrations/aura) From f6aa29ce02540edbaf3af4daf9c936a6352fde59 Mon Sep 17 00:00:00 2001 From: luisllaver <94252605+luisllaver@users.noreply.github.com> Date: Wed, 20 May 2026 13:44:28 -0300 Subject: [PATCH 12/32] Add opt-in AURA trust-check adapter (integrations/aura) From 9faa4192e5e3c2c6fff4fba234bb261fab82dcbc Mon Sep 17 00:00:00 2001 From: luisllaver <94252605+luisllaver@users.noreply.github.com> Date: Wed, 20 May 2026 13:44:30 -0300 Subject: [PATCH 13/32] Add opt-in AURA trust-check adapter (integrations/aura) From f86bc9c1661f82a38adcfa1a3d8e8e30e8856355 Mon Sep 17 00:00:00 2001 From: luisllaver <94252605+luisllaver@users.noreply.github.com> Date: Wed, 20 May 2026 13:44:31 -0300 Subject: [PATCH 14/32] Add opt-in AURA trust-check adapter (integrations/aura) (remove integrations/aura/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json) --- .../vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json | 1 - 1 file changed, 1 deletion(-) delete mode 100644 integrations/aura/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json diff --git a/integrations/aura/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json b/integrations/aura/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json deleted file mode 100644 index 66826c0..0000000 --- a/integrations/aura/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +++ /dev/null @@ -1 +0,0 @@ -{"version":"4.1.5","results":[[":adapter.test.ts",{"duration":9.982899999999972,"failed":false}]]} \ No newline at end of file From eae102579694dc218ec992e4108d5fbc08c9e83b Mon Sep 17 00:00:00 2001 From: luisllaver <94252605+luisllaver@users.noreply.github.com> Date: Wed, 20 May 2026 13:45:16 -0300 Subject: [PATCH 15/32] Add opt-in AURA trust-check adapter (integrations/aura) From 16d5955c7ca64817fc8816968a7ac6ca7d9bfd15 Mon Sep 17 00:00:00 2001 From: luisllaver <94252605+luisllaver@users.noreply.github.com> Date: Wed, 20 May 2026 13:45:18 -0300 Subject: [PATCH 16/32] Add opt-in AURA trust-check adapter (integrations/aura) From c22af6bd4a1ed6cb1db2780995c7b732fc475fd1 Mon Sep 17 00:00:00 2001 From: luisllaver <94252605+luisllaver@users.noreply.github.com> Date: Wed, 20 May 2026 13:45:19 -0300 Subject: [PATCH 17/32] Add opt-in AURA trust-check adapter (integrations/aura) From 8de1fb32f4bbee18ffd26cb4142a4d92e50d1702 Mon Sep 17 00:00:00 2001 From: luisllaver <94252605+luisllaver@users.noreply.github.com> Date: Wed, 20 May 2026 13:45:20 -0300 Subject: [PATCH 18/32] Add opt-in AURA trust-check adapter (integrations/aura) From b7e1f2d9939b9d24df5fc68a416e4fa6b170b416 Mon Sep 17 00:00:00 2001 From: luisllaver <94252605+luisllaver@users.noreply.github.com> Date: Wed, 20 May 2026 13:45:21 -0300 Subject: [PATCH 19/32] Add opt-in AURA trust-check adapter (integrations/aura) From 16e2586fa7464d3d2521f6df397f5fef70051c13 Mon Sep 17 00:00:00 2001 From: luisllaver <94252605+luisllaver@users.noreply.github.com> Date: Wed, 20 May 2026 13:45:22 -0300 Subject: [PATCH 20/32] Add opt-in AURA trust-check adapter (integrations/aura) From 78322063fca32ef9d0db5bf2de3f551f2f679122 Mon Sep 17 00:00:00 2001 From: luisllaver <94252605+luisllaver@users.noreply.github.com> Date: Wed, 20 May 2026 21:28:07 -0300 Subject: [PATCH 21/32] Add opt-in AURA trust-check adapter (integrations/aura) --- integrations/aura/adapter.test.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/integrations/aura/adapter.test.ts b/integrations/aura/adapter.test.ts index 3725ea5..a4327f9 100644 --- a/integrations/aura/adapter.test.ts +++ b/integrations/aura/adapter.test.ts @@ -122,6 +122,20 @@ describe('network failure', () => { it('gate passes on unreachable when failOpen', async () => { const v = await beforeSettle('did:aura:trusted-bot', { failOpen: true, fetchImpl: failFetch }); expect(v.verdict).toBe('unknown'); + expect(v.reachable).toBe(false); + }); + + it('failOpen does NOT pass a reachable unknown (ghost DID)', async () => { + // A reachable AURA that returns `unknown` is still rejected even with + // failOpen — failOpen only excuses transport failures. + await expect( + beforeSettle('did:aura:ghost-bot', { failOpen: true, fetchImpl: okFetch }), + ).rejects.toBeInstanceOf(AuraUntrusted); + }); + + it('reachable verdict is marked reachable', async () => { + const v = await auraVerdict('did:aura:ghost-bot', { fetchImpl: okFetch }); + expect(v.reachable).toBe(true); }); }); From 81641763649ff0184355d1618f45a56a07d82940 Mon Sep 17 00:00:00 2001 From: luisllaver <94252605+luisllaver@users.noreply.github.com> Date: Wed, 20 May 2026 21:28:08 -0300 Subject: [PATCH 22/32] Add opt-in AURA trust-check adapter (integrations/aura) --- integrations/aura/adapter.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/integrations/aura/adapter.ts b/integrations/aura/adapter.ts index 6e52d27..756287b 100644 --- a/integrations/aura/adapter.ts +++ b/integrations/aura/adapter.ts @@ -40,6 +40,13 @@ export interface AuraVerdict { hasHistory: boolean; /** per-dimension breakdown (which axis is weak), or null */ dimensions: Record | null; + /** + * False only when AURA could not be reached (network/parse failure) and the + * verdict is a synthetic `unknown`. A reachable AURA that genuinely returns + * `unknown` has `reachable: true`. `failOpen` keys on this, not on the + * verdict alone, so it can't wave through explicitly-unverified counterparties. + */ + reachable: boolean; /** the untouched JSON body */ raw: Record; } @@ -85,12 +92,13 @@ function toVerdict(did: string, body: Record): AuraVerdict { ok: verdict === 'trusted' || verdict === 'caution', hasHistory: Boolean(body.has_history), dimensions: (body.dimensions as Record | null) ?? null, + reachable: true, raw: body, }; } function unreachable(did: string, reason: string): AuraVerdict { - return { did, verdict: 'unknown', reason, score: null, ok: false, hasHistory: false, dimensions: null, raw: {} }; + return { did, verdict: 'unknown', reason, score: null, ok: false, hasHistory: false, dimensions: null, reachable: false, raw: {} }; } /** @@ -140,14 +148,16 @@ export async function auraVerdict(did: string, opts: VerdictOptions = {}): Promi * Tighten to reject brand-new agents too: * await beforeSettle(did, { allow: ['trusted', 'caution'] }); * - * failOpen: true makes an *unreachable* AURA pass through. Off by default — - * absence of evidence is not evidence of trust. + * failOpen: true makes an *unreachable* AURA pass through (transport failure + * only — a reachable AURA that returns `unknown` is still rejected). Off by + * default — absence of evidence is not evidence of trust. */ export async function beforeSettle(did: string, opts: GateOptions = {}): Promise { const allow = opts.allow ?? DEFAULT_ALLOW; const v = await auraVerdict(did, opts); if (allow.includes(v.verdict)) return v; - if (opts.failOpen && v.verdict === 'unknown' && !v.hasHistory) return v; + // failOpen only excuses a transport failure, never a reachable `unknown`. + if (opts.failOpen && !v.reachable) return v; throw new AuraUntrusted(v); } From 898852330db78956f611c3939b73735535903567 Mon Sep 17 00:00:00 2001 From: luisllaver <94252605+luisllaver@users.noreply.github.com> Date: Wed, 20 May 2026 21:28:09 -0300 Subject: [PATCH 23/32] Add opt-in AURA trust-check adapter (integrations/aura) From 823f298df65f9dda5b8ebfd2edfebc599d32902d Mon Sep 17 00:00:00 2001 From: luisllaver <94252605+luisllaver@users.noreply.github.com> Date: Wed, 20 May 2026 21:28:10 -0300 Subject: [PATCH 24/32] Add opt-in AURA trust-check adapter (integrations/aura) --- integrations/aura/README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/integrations/aura/README.md b/integrations/aura/README.md index dba5d29..2f46865 100644 --- a/integrations/aura/README.md +++ b/integrations/aura/README.md @@ -15,10 +15,10 @@ natural `beforeSettle` gate in front of `client.pay()`. Gate the settlement at the call site. No global hooks, no monkey-patching: ```ts -import { PayBotClient } from 'paybot-sdk'; +import { PayBotClient, type PaymentRequest } from 'paybot-sdk'; import { beforeSettle, AuraUntrusted } from './integrations/aura'; -const client = new PayBotClient(config); +const client = new PayBotClient(config); // your existing PayBot config async function payChecked(counterpartyDid: string, req: PaymentRequest) { try { @@ -49,9 +49,13 @@ console.log(v.verdict); // trusted | caution | high_risk | new | unknown console.log(v.reason, v.score, v.ok); // v.dimensions tells you *which* axis is weak, not just the aggregate: -if ((v.dimensions?.financial_integrity ?? 1) < 0.4) requireManualReview(); +if ((v.dimensions?.financial_integrity ?? 1) < 0.4) requireManualReview(); // placeholder for your own policy ``` +> `v.ok` reflects the *verdict class* (true for `trusted`/`caution`), not the +> outcome of `beforeSettle` — the gate's default `allow` also lets `new` +> through. Use the gate's return/throw for the decision, `v.ok` for display. + ## Verdicts | verdict | meaning | `ok` | From 8dc40e5ba8569f273ff5069db62ffc3926c6686d Mon Sep 17 00:00:00 2001 From: luisllaver <94252605+luisllaver@users.noreply.github.com> Date: Wed, 20 May 2026 21:28:11 -0300 Subject: [PATCH 25/32] Add opt-in AURA trust-check adapter (integrations/aura) From dcb5ee39e8a13ce10b1156f2c4afdcaa2140f0f4 Mon Sep 17 00:00:00 2001 From: luisllaver <94252605+luisllaver@users.noreply.github.com> Date: Wed, 20 May 2026 21:28:12 -0300 Subject: [PATCH 26/32] Add opt-in AURA trust-check adapter (integrations/aura) From 66e276e651b3cbb828b5f23d83793dbe16bd9c49 Mon Sep 17 00:00:00 2001 From: luisllaver <94252605+luisllaver@users.noreply.github.com> Date: Wed, 20 May 2026 21:58:31 -0300 Subject: [PATCH 27/32] Add opt-in AURA trust-check adapter (integrations/aura) From 751df52c3b0a327726ca06e55d6671f128579cb2 Mon Sep 17 00:00:00 2001 From: luisllaver <94252605+luisllaver@users.noreply.github.com> Date: Wed, 20 May 2026 21:58:32 -0300 Subject: [PATCH 28/32] Add opt-in AURA trust-check adapter (integrations/aura) From 8ce8cced66f8455951f1e163e0c0c8989aa00766 Mon Sep 17 00:00:00 2001 From: luisllaver <94252605+luisllaver@users.noreply.github.com> Date: Wed, 20 May 2026 21:58:35 -0300 Subject: [PATCH 29/32] Add opt-in AURA trust-check adapter (integrations/aura) From f6b1a32eba8b77878b55ad5259b10ba53673b4b6 Mon Sep 17 00:00:00 2001 From: luisllaver <94252605+luisllaver@users.noreply.github.com> Date: Wed, 20 May 2026 21:58:36 -0300 Subject: [PATCH 30/32] Add opt-in AURA trust-check adapter (integrations/aura) --- integrations/aura/README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/integrations/aura/README.md b/integrations/aura/README.md index 2f46865..bc60deb 100644 --- a/integrations/aura/README.md +++ b/integrations/aura/README.md @@ -106,6 +106,23 @@ unreachable path, and input validation. See [THREAT_MODEL.md](./THREAT_MODEL.md) — what the verdict does and does not prove, and the failure modes a verifier should account for. +## Carry the AURA badge + +Show your live trust verdict in your own README — it updates automatically and +links back to your AURA profile: + +```markdown +[![AURA Verified](https://agent.auraopenprotocol.org/badge?did=YOUR_DID)](https://agent.auraopenprotocol.org/check?did=YOUR_DID) +``` + +A shields-style badge colored by verdict (`trusted` green, `caution` amber, +`high_risk` red, `new` blue, `unknown` grey). Add `&score=1` to show the +composite score. No DID yet? The bare badge is a generic mark: + +```markdown +[![Powered by AURA](https://agent.auraopenprotocol.org/badge)](https://auraopenprotocol.org) +``` + ## What's behind the verdict [AURA Open Protocol](https://auraopenprotocol.org) — W3C DID identity plus 8 From e0a4a23e0e89562687b1a4b117d32b6668555fa7 Mon Sep 17 00:00:00 2001 From: luisllaver <94252605+luisllaver@users.noreply.github.com> Date: Wed, 20 May 2026 21:58:36 -0300 Subject: [PATCH 31/32] Add opt-in AURA trust-check adapter (integrations/aura) From bce213106af92de4d4028da22576416c8d1203b2 Mon Sep 17 00:00:00 2001 From: luisllaver <94252605+luisllaver@users.noreply.github.com> Date: Wed, 20 May 2026 21:58:37 -0300 Subject: [PATCH 32/32] Add opt-in AURA trust-check adapter (integrations/aura)