Skip to content

Add opt-in AURA trust-check adapter (integrations/aura)#10

Open
luisllaver wants to merge 32 commits into
RBKunnela:mainfrom
luisllaver:aura-trust-check-adapter
Open

Add opt-in AURA trust-check adapter (integrations/aura)#10
luisllaver wants to merge 32 commits into
RBKunnela:mainfrom
luisllaver:aura-trust-check-adapter

Conversation

@luisllaver
Copy link
Copy Markdown

@luisllaver luisllaver commented May 20, 2026

Opt-in, read-only AURA trust-check adapter — picking up the Founding Contributors invite (#3).

What it is — a integrations/aura module: a beforeSettle(did) gate to run in front of
client.pay(). Backward-looking reputation as a pre-flight axis beside PayBot's own
TRUST_VIOLATION check — kept separate so the settle decision stays auditable.

API

  • auraVerdict(did) → typed {verdict, reason, score, ...} (trusted|caution|high_risk|new|unknown)
  • beforeSettle(did) / requireTrust(did) → throws AuraUntrusted on high_risk + unknown
  • one call: GET /check?did=... (zero-auth). Does not sign, hold keys, or move USDC.

Boundary — zero deps (global fetch, Node 18+). Off by default. Fail-closed by default; set
failOpen: true to let an unreachable AURA pass. Purely additive — remove it and PayBot is unchanged.

Testsnpx vitest run --config integrations/aura/vitest.config.ts → 17 offline tests via a
fetchImpl injection seam: every verdict class, the gate's allow-list + failOpen, the unreachable
path, input validation. Strict tsc clean.

See integrations/aura/README.md (wraps client.pay()) and THREAT_MODEL.md.

Opened as draft — happy to align naming/paths with paybot-sdk conventions before you merge.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added AURA reputation gate integration for optional pre-settlement trust verification
    • Configurable verdict policies and fail-open/fail-closed behavior
    • Read-only integration with no impact on fund movement
  • Documentation

    • Added comprehensive usage guide with policy configuration options
    • Added threat model documentation
  • Tests

    • Added test coverage for verdict validation and settlement gating behavior

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 20, 2026

Warning

Rate limit exceeded

@luisllaver has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 28 minutes and 19 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3d3efb28-50d9-479b-8869-7af95c464928

📥 Commits

Reviewing files that changed from the base of the PR and between 309fd84 and bce2131.

📒 Files selected for processing (3)
  • integrations/aura/README.md
  • integrations/aura/adapter.test.ts
  • integrations/aura/adapter.ts

Walkthrough

This PR adds a complete AURA trust-check adapter to the PayBot SDK: a read-only, opt-in reputation gate that runs before settlement. The module exports auraVerdict to fetch verdicts from AURA and beforeSettle to gate settlement based on configurable verdict allow lists, with graceful fault tolerance and comprehensive offline test coverage.

Changes

AURA Trust-Check Adapter

Layer / File(s) Summary
Type contracts and adapter constants
integrations/aura/adapter.ts
Verdict type union (trusted, caution, high_risk, new, unknown), AuraVerdict interface with metadata (reason, score, ok, hasHistory, dimensions), AuraUntrusted error class, FetchLike and options types, and default constants (base URL, timeout, allow list).
Verdict fetching with fault tolerance
integrations/aura/adapter.ts, integrations/aura/adapter.test.ts
auraVerdict(did) validates DID format, calls AURA /check endpoint with timeout via injected fetch, maps response to AuraVerdict, and gracefully returns unknown verdict without throwing on network/parse failures. Tests validate verdict mapping per class, auxiliary fields, error handling, and input validation with recorded fixture responses.
Settlement gating with verdict allow list
integrations/aura/adapter.ts, integrations/aura/adapter.test.ts
beforeSettle(did, opts) fetches verdict, checks against configurable allow list (default: [trusted, caution, new]), optionally passes unreachable unknown on failOpen, and throws AuraUntrusted with rejected verdict otherwise. requireTrust exported as alias. Tests verify allow/deny behavior per verdict class, strict allow override, and fail-open configuration.
Public package exports
integrations/aura/index.ts
Entry point re-exports runtime values and types from adapter with module documentation describing opt-in reputation checking.
Documentation, threat model, and test configuration
integrations/aura/README.md, integrations/aura/THREAT_MODEL.md, integrations/aura/vitest.config.ts
README explains usage, verdicts, policy knobs, and network failure modes. Threat model documents verdict semantics, caller-responsibility failure modes (spoofed DID, stale verdict, MITM, score gaming), and data boundaries. Vitest config enables independent test execution.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately summarizes the main change: adding an opt-in AURA trust-check adapter in the integrations/aura directory. It is specific, concise, and clearly communicates the primary objective of the PR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the AURA trust-check adapter, a zero-dependency TypeScript integration for gating settlements based on counterparty reputation. The implementation includes core adapter logic, comprehensive offline tests, a threat model, and documentation. Review feedback focuses on refining the failOpen logic to better handle service unreachability, ensuring the ok property consistently reflects default gating behavior, and improving the clarity and completeness of documentation code examples by addressing missing imports and undefined variables.

Comment thread integrations/aura/adapter.ts Outdated
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;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current logic for failOpen (opts.failOpen && v.verdict === 'unknown' && !v.hasHistory) might be too broad. It allows a pass not only when the AURA service is unreachable, but also for any 'unknown' verdict where hasHistory is false (like a 'ghost' DID that is known to AURA but has no history). The documentation suggests failOpen should only apply to unreachable cases.

To make this more precise, I suggest adding a dedicated flag to the AuraVerdict object to indicate unreachability. This would make the failOpen logic more robust and aligned with its documented purpose.

This would require the following changes:

  1. Add unreachable?: boolean; to the AuraVerdict interface.
  2. Set unreachable: false in toVerdict and unreachable: true in the unreachable helper function.
  3. Update this line to check v.unreachable as shown in the suggestion.
Suggested change
if (opts.failOpen && v.verdict === 'unknown' && !v.hasHistory) return v;
if (opts.failOpen && v.unreachable) return v;

Comment thread integrations/aura/README.md Outdated
Comment on lines +18 to +23
import { PayBotClient } from 'paybot-sdk';
import { beforeSettle, AuraUntrusted } from './integrations/aura';

const client = new PayBotClient(config);

async function payChecked(counterpartyDid: string, req: PaymentRequest) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The code example in this section has a couple of issues that make it incomplete and potentially confusing for users:

  1. The PaymentRequest type is used in payChecked but is not imported.
  2. The config object used to initialize PayBotClient is not defined.

To make the example self-contained and easier to follow, please add the missing import and a placeholder for the config object.

Suggested change
import { PayBotClient } from 'paybot-sdk';
import { beforeSettle, AuraUntrusted } from './integrations/aura';
const client = new PayBotClient(config);
async function payChecked(counterpartyDid: string, req: PaymentRequest) {
import { PayBotClient, type PaymentRequest } from 'paybot-sdk';
import { beforeSettle, AuraUntrusted } from './integrations/aura';
// Replace with your actual PayBotClient configuration
const config = { apiKey: '...', botId: '...' };
const client = new PayBotClient(config);
async function payChecked(counterpartyDid: string, req: PaymentRequest) {

Comment thread integrations/aura/README.md Outdated
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();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The function requireManualReview() is not defined in this example, which might confuse readers. To improve clarity, please add a comment to indicate that this is a placeholder for the user's own policy logic.

Suggested change
if ((v.dimensions?.financial_integrity ?? 1) < 0.4) requireManualReview();
if ((v.dimensions?.financial_integrity ?? 1) < 0.4) requireManualReview(); // Example: your custom policy logic

verdict,
reason: String(body.reason ?? ''),
score: typeof body.score === 'number' ? body.score : null,
ok: verdict === 'trusted' || verdict === 'caution',
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There's a potential for confusion with the ok property. It is true only for 'trusted' and 'caution' verdicts, but the beforeSettle function's default behavior also allows 'new' verdicts. This inconsistency might lead developers to mistakenly believe that v.ok reflects the outcome of beforeSettle.

To improve clarity and consistency, consider aligning the ok property with the default gate behavior by also including 'new'. This would make the ok flag a more reliable indicator of the default gate's pass/fail status.

If you apply this change, please also update the ok column for the new verdict in integrations/aura/README.md.

Suggested change
ok: verdict === 'trusted' || verdict === 'caution',
ok: ['trusted', 'caution', 'new'].includes(verdict),

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
integrations/aura/adapter.test.ts (1)

122-125: ⚡ Quick win

Add a regression test: failOpen must not allow reachable unknown

Current coverage only verifies transport failure. Add a case where AURA is reachable and returns unknown (e.g., did:aura:ghost-bot) with failOpen: true, and assert rejection with AuraUntrusted.

Suggested test addition
 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');
   });
+
+  it('failOpen does not pass a reachable unknown verdict', async () => {
+    await expect(
+      beforeSettle('did:aura:ghost-bot', { failOpen: true, fetchImpl: okFetch }),
+    ).rejects.toBeInstanceOf(AuraUntrusted);
+  });
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@integrations/aura/adapter.test.ts` around lines 122 - 125, Add a regression
test that verifies failOpen does NOT convert a reachable AURA "unknown" response
into a pass: call the same helper used elsewhere (beforeSettle) with a subject
that AURA will respond to as unknown (e.g., 'did:aura:ghost-bot') and options
including failOpen: true and a fetchImpl that returns an "unknown" response;
assert that the call rejects with AuraUntrusted (use
expect(...).rejects.toThrow(AuraUntrusted) or equivalent). Target the test in
integrations/aura/adapter.test.ts alongside the existing unreachable failure
test and reference beforeSettle and AuraUntrusted so the test fails if behavior
regresses.
integrations/aura/THREAT_MODEL.md (1)

43-52: 💤 Low value

Optional: Add language hint to fenced code block.

The fenced code block at line 43 lacks a language specifier, triggering a markdown linter warning. While the ASCII diagram renders fine, adding a language hint improves markdown validity.

♻️ Proposed fix
 ## Trust boundary summary
 
-```
+```text
 PayBot host --(DID only, HTTPS GET)--> AURA /check --> verdict
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@integrations/aura/THREAT_MODEL.md` around lines 43 - 52, The fenced ASCII
diagram in THREAT_MODEL.md is missing a language hint; update the
triple-backtick fence around the diagram (the block containing "PayBot host
--(DID only, HTTPS GET)--> AURA /check --> verdict") to include a language
specifier such as "text" (i.e. change ``` to ```text) so the markdown linter
stops warning while keeping the content unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@integrations/aura/adapter.ts`:
- Around line 150-151: The current check allows opts.failOpen to let any v with
verdict === 'unknown' and no history pass, including valid reachable AURA
"unknown" responses; restrict this to only transport-level failures by changing
the condition to require a transport failure indicator (e.g., v.transportFailure
|| v.error || similar flag populated when the AURA call failed to reach the
service) in addition to opts.failOpen, v.verdict === 'unknown' and
!v.hasHistory, and otherwise throw new AuraUntrusted(v); update the code that
builds v if needed to set that transport-failure flag so the adapter only fails
open on transport errors.

In `@integrations/aura/README.md`:
- Around line 17-40: The README uses a relative import for beforeSettle and
AuraUntrusted from './integrations/aura' which isn't exposed by package.json, so
consumers cannot import those symbols; fix by either adding a subpath export for
"./integrations/aura" in package.json pointing to
dist/integrations/aura/index.{js,d.ts}, or re-export beforeSettle and
AuraUntrusted from the package's main entry (index.ts) so users can import them
from 'paybot-sdk', or update the README example to import from the actual public
path your package exposes; ensure the referenced symbols beforeSettle and
AuraUntrusted are exported where you add the subpath or re-export.

---

Nitpick comments:
In `@integrations/aura/adapter.test.ts`:
- Around line 122-125: Add a regression test that verifies failOpen does NOT
convert a reachable AURA "unknown" response into a pass: call the same helper
used elsewhere (beforeSettle) with a subject that AURA will respond to as
unknown (e.g., 'did:aura:ghost-bot') and options including failOpen: true and a
fetchImpl that returns an "unknown" response; assert that the call rejects with
AuraUntrusted (use expect(...).rejects.toThrow(AuraUntrusted) or equivalent).
Target the test in integrations/aura/adapter.test.ts alongside the existing
unreachable failure test and reference beforeSettle and AuraUntrusted so the
test fails if behavior regresses.

In `@integrations/aura/THREAT_MODEL.md`:
- Around line 43-52: The fenced ASCII diagram in THREAT_MODEL.md is missing a
language hint; update the triple-backtick fence around the diagram (the block
containing "PayBot host --(DID only, HTTPS GET)--> AURA /check --> verdict") to
include a language specifier such as "text" (i.e. change ``` to ```text) so the
markdown linter stops warning while keeping the content unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f1192a76-e834-4ef9-bae1-99a571457255

📥 Commits

Reviewing files that changed from the base of the PR and between a923b08 and 309fd84.

⛔ Files ignored due to path filters (1)
  • integrations/aura/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json is excluded by !**/node_modules/**
📒 Files selected for processing (6)
  • integrations/aura/README.md
  • integrations/aura/THREAT_MODEL.md
  • integrations/aura/adapter.test.ts
  • integrations/aura/adapter.ts
  • integrations/aura/index.ts
  • integrations/aura/vitest.config.ts

Comment thread integrations/aura/adapter.ts Outdated
Comment on lines +17 to +40
```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.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if package.json has subpath exports configured for integrations/aura
if [ -f package.json ]; then
  echo "=== Checking package.json exports configuration ==="
  jq -r '.exports // "No exports field"' package.json
else
  echo "No package.json found at root"
fi

# Check for any existing integration imports in the codebase
echo -e "\n=== Searching for existing integration import patterns ==="
rg -n --type=ts "from ['\"].*integrations/" -C 2 || echo "No existing integration imports found"

Repository: RBKunnela/paybot-sdk

Length of output: 570


Fix import path: package.json lacks subpath exports for the documented example.

The README example (line 19) shows import { beforeSettle, AuraUntrusted } from './integrations/aura', but package.json currently exports only the root entry point. SDK consumers cannot use the relative import path as documented.

Either:

  1. Add subpath export to package.json: "./integrations/aura": { "import": "./dist/integrations/aura/index.js", "types": "./dist/integrations/aura/index.d.ts" }
  2. Or re-export from the main index.ts so users import from 'paybot-sdk'
  3. Or update the example to match the actual consumable import path

Confirm which pattern aligns with your SDK's public API design.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@integrations/aura/README.md` around lines 17 - 40, The README uses a relative
import for beforeSettle and AuraUntrusted from './integrations/aura' which isn't
exposed by package.json, so consumers cannot import those symbols; fix by either
adding a subpath export for "./integrations/aura" in package.json pointing to
dist/integrations/aura/index.{js,d.ts}, or re-export beforeSettle and
AuraUntrusted from the package's main entry (index.ts) so users can import them
from 'paybot-sdk', or update the README example to import from the actual public
path your package exposes; ensure the referenced symbols beforeSettle and
AuraUntrusted are exported where you add the subpath or re-export.

@luisllaver
Copy link
Copy Markdown
Author

Thanks @coderabbitai @gemini-code-assist — good catch on failOpen. Fixed: it now only excuses a transport failure (a synthetic unknown from an unreachable AURA), never a reachable unknown such as a ghost DID. Added a reachable flag to the verdict and tests covering both paths (gate still rejects a reachable unknown even with failOpen: true). Also tightened the README example (typed PaymentRequest import, placeholder comments) and clarified that v.ok is the verdict class, not the gate outcome. Pushed — 19 vitest tests, strict tsc clean.

@gemini-code-assist
Copy link
Copy Markdown

Thanks for the update, @luisllaver. The distinction between transport failures and reachable unknown verdicts, combined with the new reachable flag, significantly improves the robustness of the failOpen logic. The expanded test suite (19 tests) and the clarified documentation in the README and threat model provide excellent coverage and clarity for future maintainers. The implementation now looks very solid.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
cat integrations/aura/adapter.ts
echo "---TEST FILE---"
cat integrations/aura/adapter.test.ts
echo "---README---"
cat integrations/aura/README.md

Length of output: 16584


@luisllavar The reachable flag partitioning is exactly right. Walking through the critical paths:

  • Transport failure + failOpen: true: failFetchunreachable()reachable: false!v.reachable is true → gate passes ✅
  • Ghost DID + failOpen: true: okFetchtoVerdict()reachable: true!v.reachable is false → gate still throws AuraUntrusted
  • Malformed HTTP 200 body (bonus edge): toVerdict() falls back to unknown but reachable: truefailOpen won't excuse it either — correct behaviour with no extra code ✅

The test coverage hits both reachable paths explicitly ('failOpen does NOT pass a reachable unknown (ghost DID)' and 'reachable verdict is marked reachable'). Gate semantics are airtight.

One small doc inconsistency: the README still says "17 tests" at the bottom, but you're now at 19. Worth a one-line fix before merge.

Everything else looks clean — the v.ok clarification, the typed PaymentRequest import, and the inline JSDoc on reachable in the interface are all good additions.

@RBKunnela RBKunnela added the friendlyai-review-refresh Trigger FriendlyAI Review on existing PRs label May 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

friendlyai-review-refresh Trigger FriendlyAI Review on existing PRs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants