Metadata-Version: 2.4
Name: robustrep
Version: 0.2.2
Summary: Robust, transport-agnostic reputation scoring for AI agents
Author: Dong Wang
License: Apache-2.0
Project-URL: Homepage, https://github.com/iamdongwang/robustrep
Project-URL: Repository, https://github.com/iamdongwang/robustrep
Project-URL: Issues, https://github.com/iamdongwang/robustrep/issues
Project-URL: Report, https://iamdongwang.github.io/robustrep/reports/latest/report.md
Classifier: Development Status :: 3 - Alpha
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security
Classifier: Topic :: Scientific/Engineering :: Information Analysis
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pandas<4,>=2.0
Requires-Dist: numpy>=1.24
Requires-Dist: eth-abi>=5.0
Requires-Dist: eth-hash[pycryptodome]>=0.5
Requires-Dist: requests>=2.31
Requires-Dist: urllib3>=2.8
Requires-Dist: tabulate>=0.9
Requires-Dist: matplotlib>=3.7
Requires-Dist: typer>=0.12
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: pytest-cov>=5; extra == "dev"
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"
Requires-Dist: pip-audit; extra == "dev"
Dynamic: license-file

# robustrep

Robust, transport-agnostic reputation scoring for AI agents.

Feed it any table of `(rater, ratee, value, scale, tag, ts, evidence_uri, source)` and it returns a
robust score per ratee that resists Sybil raters and evidence-free ratings. Ships with a Base-chain
ERC-8004 adapter and a reproducible report.

Status: v0.1.1 released on PyPI. Design spec and plan live in the parent workspace; a summary is in `reports/latest/report.md`.
Release notes, including the security fixes in 0.1.1: [CHANGELOG.md](CHANGELOG.md).

## Install

    pip install robustrep

## Library

```python
import pandas as pd
from robustrep import Config, score

records = pd.DataFrame([...])  # columns: rater, ratee, value, scale, tag, ts, evidence_uri, source
result = score(records, Config())
```

## Base ERC-8004 report

    robustrep fetch  --db data/base.db          # pull events (resumable)
    robustrep score  --db data/base.db --out scores.csv
    robustrep report --db data/base.db --out-dir reports

The default endpoint list is rotated automatically and the `eth_getLogs` window adapts per
endpoint; `--rpc-url` (repeatable) replaces the list and `--chunk` caps the window.
RPC endpoint hostnames appear in the public report and `scores.json`; do not use an endpoint whose hostname embeds an account id or key.

Rater profiles (first-seen time and funder per rater, for sybil clustering) come from Alchemy when
`ALCHEMY_API_KEY` is set — its free plan covers Base (2.5 requests/sec; ~13k raters in about 1.5 h).
Blockscout closed its free Base API in 2026-10 and Etherscan's free plan never covered Base, so Alchemy
is now the practical source. Pick one explicitly with
`fetch --profile-source {auto,alchemy,blockscout,etherscan,none}`; `auto` prefers an Etherscan key, then
an Alchemy key, then keyless Blockscout.
The Alchemy key is read from the environment only (no flag), redacted from urllib3's debug log and
from robustrep's own log records that echo server text, and never appears in an error message —
Alchemy puts it in the request URL's path, so nothing that quotes a URL is ever raised or logged.

A rater's `funder` is "who controls or funded it", by one of two rules, recorded per row in
`raters.funder_kind`. `transfer`: the `from` of the address's first incoming transfer (the original
rule). `owner`: since October 2026 nearly every new rater on Base is a smart-contract account that
never received a transfer (230/230 sampled on 2026-10-13; 55/60 are Coinbase Smart Wallets), so
`fetch` runs a second pass that reads the account's controlling key over plain RPC (`ownerAtIndex(0..3)`,
then `owner()`; one 50-address batch per chunk, each entry's outcome its own, a reverting call never
retried) and stores it as the funder in its own namespace (`owner:<address>`) — on the sampled wallets
55 accounts resolved to only 4 keys. Wallets sharing a key cluster exactly as wallets sharing a first
funder do, but only among themselves: the key is whatever the rater's own contract reports, so it is
never compared with a transfer-kind funder. The bundlers and paymaster behind their first transactions
are shared infrastructure and are never used (nor are internal transfers, which would include a
paymaster's gas advance). An account with no owner is marked `none` (passkey-controlled accounts land
here) and not looked up again; a legacy row's `first_seen_ts` is preserved. The report's Provenance
lists the row count per kind.

Latest scores: `reports/latest/scores.json`, also served at
https://iamdongwang.github.io/robustrep/reports/latest/scores.json

## Security considerations

Everything robustrep reads is written by untrusted parties: on-chain values, tags, evidence URIs and
addresses, plus whatever the RPC/Blockscout endpoints and evidence hosts return.

`robustrep fetch` makes outbound HTTP from *your* machine to evidence URIs chosen by the rated party.
Those go through an SSRF guard (scheme/host allowlist, parser cross-check, canonical rebuild, refusal
of private/reserved addresses, manual redirects, a size cap and per-socket-operation timeouts — not a
wall-clock bound). Its one known SSRF bypass, DNS rebinding, is unmitigated in v0.1 — so run `fetch`
on a host with no privileged reach into an internal network. API keys should come from the
environment (`ETHERSCAN_API_KEY`), never the source tree; `--etherscan-key` is an escape hatch that
takes precedence over the environment and is visible to anyone who can run `ps`.

Full threat model and private reporting instructions: [SECURITY.md](SECURITY.md).
