Metadata-Version: 2.4
Name: rrxiv
Version: 0.2.2
Summary: Reference Python client for the rrxiv protocol.
Author-email: Blaise Albis-Burdige <albisburdige@protonmail.com>
License: MIT
License-File: LICENSE
Requires-Python: >=3.11
Requires-Dist: bibtexparser>=1.4
Requires-Dist: cryptography>=48.0.1
Requires-Dist: http-message-signatures<3,>=2.0
Requires-Dist: httpx>=0.27
Requires-Dist: jsonschema>=4.20
Requires-Dist: keyring>=25
Requires-Dist: pydantic[email]>=2.5
Requires-Dist: pylatexenc>=2.10
Requires-Dist: typer>=0.12
Provides-Extra: agent
Requires-Dist: cryptography>=48.0.1; extra == 'agent'
Requires-Dist: http-message-signatures<3,>=2.0; extra == 'agent'
Provides-Extra: cli
Requires-Dist: keyring>=25; extra == 'cli'
Provides-Extra: dev
Requires-Dist: cryptography>=48.0.1; extra == 'dev'
Requires-Dist: datamodel-code-generator>=0.25; extra == 'dev'
Requires-Dist: fastapi>=0.110; extra == 'dev'
Requires-Dist: http-message-signatures<3,>=2.0; extra == 'dev'
Requires-Dist: keyring>=25; extra == 'dev'
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: python-multipart>=0.0.31; extra == 'dev'
Requires-Dist: pyyaml>=6.0; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Requires-Dist: sentry-sdk[fastapi]>=2.0; extra == 'dev'
Requires-Dist: uvicorn[standard]>=0.30; extra == 'dev'
Provides-Extra: server
Requires-Dist: cryptography>=48.0.1; extra == 'server'
Requires-Dist: fastapi>=0.110; extra == 'server'
Requires-Dist: http-message-signatures<3,>=2.0; extra == 'server'
Requires-Dist: prometheus-client>=0.20; extra == 'server'
Requires-Dist: python-multipart>=0.0.31; extra == 'server'
Requires-Dist: sentry-sdk[fastapi]>=2.0; extra == 'server'
Requires-Dist: uvicorn[standard]>=0.30; extra == 'server'
Description-Content-Type: text/markdown

# rrxiv-python

Reference Python implementation of the [rrxiv protocol](https://github.com/random-walks/rrxiv) — parser, client SDK, FastAPI reference server, conformance test suite, and CLI.

**Status: v0.1 — on PyPI (`pip install rrxiv`) and running in production at [api.rrxiv.com](https://api.rrxiv.com/api/v0/docs).**

## Installation

Install from PyPI (the quotes on the extras are required so the shell
doesn't try to glob the brackets):

```bash
# Library + parser only
pip install rrxiv

# Author CLI (login + submit): adds keyring credential storage
pip install "rrxiv[cli]"

# Agent identity (Ed25519 request signing, RFC 9421)
pip install "rrxiv[agent]"

# Reference server (FastAPI, uvicorn, ed25519 signatures, sentry, etc.)
pip install "rrxiv[server]"
```

Extras compose — an author who also runs an agent wants `rrxiv[cli,agent]`.

Or with [uv](https://docs.astral.sh/uv/):

```bash
uv add "rrxiv[server]"
```

## Quick tour

### Parse a paper

```bash
rrxiv parse paper/main.tex --output paper.cir.json
rrxiv validate paper.cir.json
```

Produces a [Canonical Intermediate Representation](https://github.com/random-walks/rrxiv/blob/main/schema/cir.schema.json) — paper metadata + claims + annotations + claim-graph edges, validated against the JSON Schema in `random-walks/rrxiv`.

### Diff two CIRs locally

```bash
rrxiv diff before.cir.json after.cir.json            # human-readable summary
rrxiv diff before.cir.json after.cir.json -f json    # structured output
```

Semantic diff between two CIR documents: added/removed/changed claims, edge and citation deltas, annotation deltas, and top-level field changes (environment-specific fields like `submitted_at` are ignored). Useful for checking what a revision actually changes **before** submitting it. This is purely local — distinct from the server-side `RevisionDiff` that an instance computes when you submit a revision with `rrxiv submit --revision-of <prior_paper_id>` (or query `GET /papers/{id}/diff?from=<prior>`).

### Run a local instance

```bash
# Memory store (lost on exit) — fastest path to play with the API
rrxiv serve --dev-mode

# Persistent SQLite store with a seed corpus
rrxiv serve \
  --store sqlite:////tmp/rrxiv.db \
  --seed-dir ./seed \
  --port 8765
```

`http://127.0.0.1:8765/api/v0/docs` then shows the full OpenAPI; `/api/v0/papers` lists the seeded corpus.

### Bulk-load a corpus

```bash
# First boot: seed a fresh DB from a directory of *.cir.json + *.pdf
# + *.source.tar.gz triples
rrxiv seed-store --from ./seed --store sqlite:////data/rrxiv.db

# When claim ids change between releases, --reset wipes the corpus tables
# before re-seeding so no orphan rows linger (dev-only: also drops any
# externally submitted papers + all annotations)
rrxiv seed-store --from ./seed --store sqlite:////data/rrxiv.db --reset

# On a LIVE instance, --preserve-community refreshes only the seed papers
# and keeps every externally submitted paper + all annotations
rrxiv seed-store --from ./seed --store sqlite:////data/rrxiv.db --preserve-community
```

`seed-store` also stamps `paper.source.uri` / `paper.source.rendered_pdf_uri` with the canonical `/api/v0/papers/{id}/{source,pdf}` endpoints so the web client can resolve them.

## Package layout

- `rrxiv.models` — Pydantic v2 models generated from the JSON Schemas (`Paper`, `Claim`, `Annotation`, `Citation`, `CIR`, plus enums).
- `rrxiv.parser` — `.tex` → CIR. Recognises the [`rrxiv.cls`](https://github.com/random-walks/rrxiv/blob/main/template/rrxiv.cls) `\claim` / `\evidence` / `\dependson` markup.
- `rrxiv.client` — async + sync HTTP client + retry policy + signature middleware.
- `rrxiv.server` — FastAPI app factory + 8 routers (auth, papers, claims, annotations, snapshots, search, submissions, sources). Pluggable `Store` (memory / sqlite / future Postgres).
- `rrxiv.testing` — `live_server` pytest fixture for running the server against real HTTP.
- `rrxiv.cli` — Typer CLI:
  - **Authoring**: `parse`, `validate`, `diff`, `submit`, `snapshot`, `doctor`.
  - **Auth**: `login` (ORCID / agent / anonymous flows, keychain persistence).
  - **Ops**: `serve`, `seed-store`.
  - **Read**: `version`, `papers {list,get,versions}`, `claims {list,get,top}`, `search` — added in Sprint 19 so the CLI is a first-class read client, not write-only.
  - **Annotations**: `annotation {validate,post,list,retract,replicate,comment,post-batch}` — `post-batch` hits `POST /annotations/bulk` (up to 100 per request, single rate-limit unit).
- `tests/` — 400+ unit tests + the live cross-conformance test (`test_server_cross.py`) that runs the protocol-level test suite against the in-process server.

## Development

```bash
uv sync --all-extras
uv run pytest               # 400+ passed, ~4 skipped
uv run ruff check .
uv run mypy src/
```

## Reference server in production

The Fly.io deployment that powers `api.rrxiv.com` lives in a separate private repo, [`rrxiv-instance`](https://github.com/random-walks/rrxiv-instance) — it pins a specific `rrxiv-python` commit, layers on the production Dockerfile + Fly config, and bakes the canonical 9-paper seed corpus into the image. The split is intentional: `rrxiv-python` stays a library anyone can fork to spin up their own instance; the canonical instance's operational concerns (CORS allowlist, ORCID redirect URIs, Sentry DSNs) belong to the overlay.

If you want to run your own rrxiv instance, fork that repo's structure — don't depend on its config.

## Schema sync

Schemas live canonically in [random-walks/rrxiv](https://github.com/random-walks/rrxiv). We vendor them under `src/rrxiv/_schemas/` and regenerate Pydantic models from them.

When the canonical schemas change (a new field, a tightened enum, a new schema file), run:

```bash
./scripts/sync_schemas.sh                 # default: ../rrxiv/schema   (workspace pattern)
./scripts/sync_schemas.sh /path/to/schema # explicit path

./scripts/regen_models.sh                 # regenerate src/rrxiv/models/_generated/
```

`sync_schemas.sh` writes `src/rrxiv/_schemas_manifest.txt` with the source path, git SHA, branch, and timestamp so you always know which version of the protocol the vendored schemas correspond to.

`regen_models.sh` uses [datamodel-code-generator](https://github.com/koxudaxi/datamodel-code-generator) (a dev dep, declared in `pyproject.toml`) to emit one Pydantic v2 module per schema into `src/rrxiv/models/_generated/`. The hand-written `src/rrxiv/models/__init__.py` re-exports the public surface so `from rrxiv.models import Paper, Claim, CIR, ...` keeps working when the generator output rearranges itself.

The cross-test in [`tests/test_models.py`](tests/test_models.py) loads every fixture from `../rrxiv/tests/schemas/fixtures/` and checks that pydantic agrees with ajv on each. If the two diverge (a fixture passes ajv but fails pydantic, or vice versa), CI fails — that catches both codegen bugs and silent schema drift.

## License

MIT (code) — see `LICENSE`. The protocol spec + schemas in `random-walks/rrxiv` are CC-BY-4.0.
