Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
7c246e3
Phase 1: Critical fixes + documentation reconciliation
tlongwell-block Apr 3, 2026
1fefea6
Phase 2a: Shared relay protocol, ApiError, MCP helpers, workspace lints
tlongwell-block Apr 3, 2026
2713a09
Phase 2b: Cross-crate relay protocol migration + SideEffectError
tlongwell-block Apr 3, 2026
81e7b0b
Phase 3: God function decomposition + DRY fixes
tlongwell-block Apr 3, 2026
2f50554
Phase 4: Desktop accessibility fixes + test infrastructure
tlongwell-block Apr 3, 2026
cd5ebb9
Phase 5: Proxy hardening + workspace safety
tlongwell-block Apr 3, 2026
29b072d
Fix crossfire findings: ApiError info leak + duplicate message + hash…
tlongwell-block Apr 3, 2026
d60222f
Crossfire iteration: fix stale replay, harden relay_protocol, sanitiz…
tlongwell-block Apr 3, 2026
5c885cc
Remove workspace clippy lint (incompatible with CI -D warnings)
tlongwell-block Apr 3, 2026
ce5cfab
Fix CI: restore missing nostr imports, fix borrow issues, allow dead_…
tlongwell-block Apr 3, 2026
cfa7222
Fix integration tests: xmax cast, ApiError response format, domain-sp…
tlongwell-block Apr 3, 2026
2a3b857
Fix clippy: matches! pattern can't use .into() — use wildcard
tlongwell-block Apr 3, 2026
a9d50eb
Convert all token error branches to ApiError::Custom for stable domai…
tlongwell-block Apr 3, 2026
beb08fe
Fix codex blockers: ORDER BY in stale-replay, get_channel error disti…
tlongwell-block Apr 3, 2026
0a0e3e8
Merge origin/main into quality-improvement
tlongwell-block Apr 3, 2026
8516e24
Fix merge: export AppView/MainView types from useViewRouter, import i…
tlongwell-block Apr 3, 2026
4ac7dff
Fix crossfire findings: restore API contracts, harden TOCTOU, cap anc…
tlongwell-block Apr 4, 2026
3e64936
Merge origin/main into quality-improvement
tlongwell-block Apr 4, 2026
ad88e85
Rename ApiError::Custom to ApiError::Coded with named fields
tlongwell-block Apr 4, 2026
db635ad
Split ValidationError from SideEffectError, fix fail-open in archived…
tlongwell-block Apr 4, 2026
877d76e
Fix ValidationError: map DbError client variants to Rejected, not Infra
tlongwell-block Apr 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ code style, PR process, architecture), see [CONTRIBUTING.md](CONTRIBUTING.md).
crates/
sprout-relay # WebSocket relay server — main entry point
sprout-core # Core types, event verification, filter matching
sprout-db # MySQL event store and data access layer
sprout-db # Postgres event store and data access layer
sprout-auth # Authentication and authorization
sprout-pubsub # Redis pub/sub fan-out, presence, typing indicators
sprout-mcp # MCP server providing AI agent tools
Expand Down Expand Up @@ -53,7 +53,7 @@ Run `just ci` before every PR. It runs: `fmt`, `clippy`, unit tests, desktop
build, and Tauri check. All must pass.

Run `just test` for integration tests if you touched `sprout-relay`,
`sprout-db`, or `sprout-auth` — these require a running MySQL and Redis.
`sprout-db`, or `sprout-auth` — these require a running Postgres and Redis.

Additional rules:
- No `unsafe` code
Expand Down Expand Up @@ -93,7 +93,7 @@ check existing reply handlers for the pattern.

```bash
just test-unit # unit tests, no infrastructure needed
just test # full integration suite (requires MySQL + Redis)
just test # full integration suite (requires Postgres + Redis)
```

E2E tests live in `crates/sprout-test-client/tests/`:
Expand Down
147 changes: 82 additions & 65 deletions ARCHITECTURE.md

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ unacceptable behavior to **conduct@sprout-relay.org**.
| Rust | 1.88+ | Install via [rustup](https://rustup.rs/) |
| Node.js | 24+ | Required for desktop app commands and `just ci` |
| pnpm | 10+ | Required for desktop app commands and `just ci` |
| Docker | 24+ | For MySQL, Redis, Typesense |
| Docker | 24+ | For Postgres, Redis, Typesense |
| `just` | latest | Task runner — `cargo install just` |
| `lefthook` | latest | Optional; run `lefthook install` for local Git hooks |
| `sqlx-cli` | latest | Optional; `just migrate` falls back to `docker exec` |
Expand Down Expand Up @@ -76,7 +76,7 @@ just setup
lefthook install
```

`just setup` starts Docker services (MySQL on `:3306`, Redis on `:6379`,
`just setup` starts Docker services (Postgres on `:5432`, Redis on `:6379`,
Typesense on `:8108`, Adminer on `:8082`, Keycloak on `:8180` for local
OAuth/OIDC testing) and runs all pending database migrations.

Expand Down Expand Up @@ -280,7 +280,7 @@ The short version:
```
sprout-relay ← WebSocket server, REST API, event ingestion
sprout-core ← Shared types, event verification, filter matching
sprout-db ← MySQL access layer (sqlx)
sprout-db ← Postgres access layer (sqlx)
sprout-auth ← NIP-42 + OIDC JWT + API token scopes
sprout-pubsub ← Redis fan-out
sprout-search ← Typesense full-text search
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -111,5 +111,8 @@ sprout-mcp = { path = "crates/sprout-mcp" }
sprout-proxy = { path = "crates/sprout-proxy" }
sprout-huddle = { path = "crates/sprout-huddle" }
sprout-workflow = { path = "crates/sprout-workflow" }
sprout-media = { path = "crates/sprout-media" }
sprout-media = { path = "crates/sprout-media", features = ["axum"] }
sprout-sdk = { path = "crates/sprout-sdk" }

# [workspace.lints.clippy]
# unwrap_used = "warn" # Deferred: ~610 existing unwrap() calls; -D warnings in CI turns this into a hard error.
10 changes: 5 additions & 5 deletions NOSTR.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ cargo run -p sprout-relay & # relay on :3000

# 4. Add a pubkey to the allowlist (if enabled)
# Insert directly — there is no CLI command for this yet.
mysql -u sprout -psprout_dev sprout -e \
"INSERT INTO pubkey_allowlist (pubkey) VALUES (UNHEX('<64-char-hex-pubkey>'))"
psql -U sprout -d sprout -c \
"INSERT INTO pubkey_allowlist (pubkey) VALUES (decode('<64-char-hex-pubkey>', 'hex'))"

# 5. Connect any NIP-29 + NIP-42 client to ws://localhost:3000
```
Expand Down Expand Up @@ -92,9 +92,9 @@ relay to specific external Nostr identities without granting full Okta/API-token
- Auth failure returns generic `auth-required: verification failed` (no allowlist-specific message).
- Manage the allowlist via direct SQL (no CLI command yet):
```sql
INSERT INTO pubkey_allowlist (pubkey) VALUES (UNHEX('<64-char-hex-pubkey>'));
DELETE FROM pubkey_allowlist WHERE pubkey = UNHEX('<64-char-hex-pubkey>');
SELECT HEX(pubkey), added_at, note FROM pubkey_allowlist;
INSERT INTO pubkey_allowlist (pubkey) VALUES (decode('<64-char-hex-pubkey>', 'hex'));
DELETE FROM pubkey_allowlist WHERE pubkey = decode('<64-char-hex-pubkey>', 'hex');
SELECT encode(pubkey, 'hex'), added_at, note FROM pubkey_allowlist;
```

### Group Discovery
Expand Down
30 changes: 28 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,17 @@ append-only and audited.
| NIP | Title | Status |
|-----|-------|--------|
| [NIP-01](https://github.com/nostr-protocol/nips/blob/master/01.md) | Basic protocol flow — events, filters, subscriptions | ✅ Implemented |
| [NIP-05](https://github.com/nostr-protocol/nips/blob/master/05.md) | Mapping Nostr keys to DNS-based internet identifiers | ✅ Implemented |
| [NIP-09](https://github.com/nostr-protocol/nips/blob/master/09.md) | Event deletion | ✅ Implemented |
| [NIP-10](https://github.com/nostr-protocol/nips/blob/master/10.md) | Conventions for clients' use of `e` and `p` tags in text events | ✅ Implemented |
| [NIP-11](https://github.com/nostr-protocol/nips/blob/master/11.md) | Relay information document | ✅ Implemented |
| [NIP-17](https://github.com/nostr-protocol/nips/blob/master/17.md) | Private Direct Messages | ✅ Implemented |
| [NIP-25](https://github.com/nostr-protocol/nips/blob/master/25.md) | Reactions | ✅ Implemented |
| [NIP-28](https://github.com/nostr-protocol/nips/blob/master/28.md) | Public chat channels | ✅ Via `sprout-proxy` (kind translation) |
| [NIP-29](https://github.com/nostr-protocol/nips/blob/master/29.md) | Relay-based groups | ✅ Partial (kinds 9000–9008 implemented; 9009, 9021 deferred) |
| [NIP-42](https://github.com/nostr-protocol/nips/blob/master/42.md) | Authentication of clients to relays | ✅ Implemented |
| [NIP-50](https://github.com/nostr-protocol/nips/blob/master/50.md) | Search capability | ✅ Implemented |
| [NIP-98](https://github.com/nostr-protocol/nips/blob/master/98.md) | HTTP Auth | ✅ Implemented |

## Architecture

Expand Down Expand Up @@ -96,7 +102,7 @@ append-only and audited.
**Agent interface**
| Crate | Role |
|-------|------|
| `sprout-mcp` | stdio MCP server — 43 tools for messages, channels, workflows, and feed |
| `sprout-mcp` | stdio MCP server — 44 tools for messages, channels, workflows, and feed |
| `sprout-acp` | ACP harness — bridges Sprout relay events to AI agents over stdio (goose, codex, claude code) |
| `sprout-workflow` | YAML-as-code workflow engine — triggers, actions, approval gates, execution traces |
| `sprout-huddle` | LiveKit integration — voice/video session tokens for channel participants |
Expand All @@ -106,9 +112,16 @@ append-only and audited.
|-------|------|
| `sprout-proxy` | NIP-28 compatibility proxy — standard Nostr clients (Coracle, nak, Amethyst) read/write Sprout channels via kind translation, shadow keypairs, and guest auth. See [NOSTR.md](NOSTR.md) |

**Shared libraries**
| Crate | Role |
|-------|------|
| `sprout-sdk` | Typed Nostr event builders — used by sprout-mcp and sprout-cli |
| `sprout-media` | Blossom/S3 media storage |

**Tooling**
| Crate | Role |
|-------|------|
| `sprout-cli` | Agent-first CLI for interacting with the relay |
| `sprout-admin` | CLI for minting API tokens and listing active credentials |
| `sprout-test-client` | WebSocket test harness for integration tests |

Expand Down Expand Up @@ -227,10 +240,23 @@ Copy `.env.example` to `.env` and adjust as needed. All defaults work out of the
| `SPROUT_PROXY_SALT` | — | Hex 32-byte salt for shadow key derivation |
| `SPROUT_PROXY_API_TOKEN` | — | Sprout API token with `proxy:submit` scope |
| `SPROUT_PROXY_ADMIN_SECRET` | — | Bearer secret for proxy admin endpoints (optional — omit for dev mode) |
| `SPROUT_CORS_ORIGINS` | `*` | Allowed CORS origins for REST API |
| `SPROUT_HEALTH_PORT` | — | Port for health check endpoint (separate from main bind) |
| `SPROUT_MAX_CONCURRENT_HANDLERS` | `1024` | Max concurrent EVENT/REQ handlers |
| `SPROUT_MAX_CONNECTIONS` | — | Max simultaneous WebSocket connections |
| `SPROUT_MAX_GIF_BYTES` | — | Max GIF upload size (media) |
| `SPROUT_MAX_IMAGE_BYTES` | — | Max image upload size (media) |
| `SPROUT_MEDIA_BASE_URL` | — | Public base URL for media files |
| `SPROUT_MEDIA_SERVER_DOMAIN` | — | Domain for media server |
| `SPROUT_METRICS_PORT` | — | Port for Prometheus metrics endpoint |
| `SPROUT_PUBKEY_ALLOWLIST` | — | Comma-separated pubkeys allowed to connect (empty = all) |
| `SPROUT_SEND_BUFFER` | — | WebSocket send buffer size |
| `SPROUT_UDS_PATH` | — | Unix domain socket path (alternative to TCP) |
| `OKTA_JWKS_URI` | — | Okta JWKS endpoint URI for JWT verification |

## MCP Tools

The `sprout-mcp` server exposes 43 tools over stdio, covering messaging, channels, threads,
The `sprout-mcp` server exposes 44 tools over stdio, covering messaging, channels, threads,
reactions, DMs, workflows, search, profiles, presence, and more. Agents discover tools
automatically via the MCP protocol — see [AGENTS.md](AGENTS.md) for integration details.

Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ membership.
### Append-Only Audit Log

All events are written to a tamper-evident audit log (`sprout-audit`). Each
log entry is chained to the previous one via an HMAC, making retroactive
log entry is chained to the previous one via a SHA-256 hash, making retroactive
modification detectable. The audit log is designed for SOX-grade compliance
and eDiscovery.

Expand Down
43 changes: 34 additions & 9 deletions TESTING.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Sprout Testing Guide

This guide enables an AI agent (the **operator**) to run the full Sprout test suite: automated `cargo test` suites and a three-agent multi-agent E2E run that exercises all 43 MCP tools against a live relay.
This guide enables an AI agent (the **operator**) to run the full Sprout test suite: automated `cargo test` suites and a three-agent multi-agent E2E run that exercises all 44 MCP tools against a live relay.

## Two Test Modes

| Mode | What It Does | When to Use |
|------|-------------|-------------|
| **Automated** (`cargo test`) | Unit tests + REST/WebSocket/MCP integration tests | Fast CI check; verify no unit regressions |
| **Multi-Agent E2E** | Three agents (Alice, Bob, Charlie) run via `sprout-acp` harness, exercising all 43 MCP tools via real Nostr identities | Before merging relay/MCP/auth changes; full regression run; exploring new features |
| **Multi-Agent E2E** | Three agents (Alice, Bob, Charlie) run via `sprout-acp` harness, exercising all 44 MCP tools via real Nostr identities | Before merging relay/MCP/auth changes; full regression run; exploring new features |

Run both modes for a complete regression check. Run automated-only for a fast sanity check.

Expand All @@ -27,7 +27,7 @@ Run both modes for a complete regression check. Run automated-only for a fast sa
- [3.7 Expected Results](#37-expected-results)
4. [Advanced: ACP Harness Scenarios](#4-advanced-acp-harness-scenarios)
5. [Workflow YAML Reference](#5-workflow-yaml-reference)
6. [The 43 MCP Tools](#6-the-43-mcp-tools)
6. [The 44 MCP Tools](#6-the-44-mcp-tools)
7. [Cleanup](#7-cleanup)
8. [Known Issues / Troubleshooting](#8-known-issues--troubleshooting)
9. [Proxy Tests](#9-proxy-tests)
Expand Down Expand Up @@ -168,24 +168,49 @@ Then run the integration suites:
RELAY_URL=ws://localhost:3000 \
cargo test -p sprout-test-client --test e2e_rest_api -- --ignored

# WebSocket relay integration tests (14 tests)
# WebSocket relay integration tests (27 tests)
RELAY_URL=ws://localhost:3000 \
cargo test -p sprout-test-client --test e2e_relay -- --ignored

# MCP server integration tests (14 tests)
RELAY_URL=ws://localhost:3000 \
cargo test -p sprout-test-client --test e2e_mcp -- --ignored

# Media integration tests (7 tests)
RELAY_URL=ws://localhost:3000 \
cargo test -p sprout-test-client --test e2e_media -- --ignored

# Extended media tests (18 tests)
RELAY_URL=ws://localhost:3000 \
cargo test -p sprout-test-client --test e2e_media_extended -- --ignored

# Nostr interop tests (15 tests)
RELAY_URL=ws://localhost:3000 \
cargo test -p sprout-test-client --test e2e_nostr_interop -- --ignored

# Token auth tests (20 tests)
RELAY_URL=ws://localhost:3000 \
cargo test -p sprout-test-client --test e2e_tokens -- --ignored

# Workflow integration tests (7 tests)
RELAY_URL=ws://localhost:3000 \
cargo test -p sprout-test-client --test e2e_workflows -- --ignored
```

### Expected Results

```
test result: ok. 40 passed; 0 failed; 0 ignored ← REST API
test result: ok. 14 passed; 0 failed; 0 ignored ← relay
test result: ok. 27 passed; 0 failed; 0 ignored ← relay
test result: ok. 14 passed; 0 failed; 0 ignored ← MCP
test result: ok. 7 passed; 0 failed; 0 ignored ← media
test result: ok. 18 passed; 0 failed; 0 ignored ← media extended
test result: ok. 15 passed; 0 failed; 0 ignored ← nostr interop
test result: ok. 20 passed; 0 failed; 0 ignored ← tokens
test result: ok. 7 passed; 0 failed; 0 ignored ← workflows
```

All 68 integration tests pass (across the three suites above). An additional 7 workflow integration tests exist in `e2e_workflows.rs` — run them separately if workflow changes are involved. If any fail, check that the relay is running and Docker services are healthy before proceeding to E2E.
All 148 integration tests pass across 8 test files. If any fail, check that the relay is running and Docker services are healthy before proceeding to E2E.

---

Expand All @@ -203,7 +228,7 @@ Operator (you)
Sprout Relay ──WS (NIP-01)──► sprout-acp (harness) ──stdio (ACP)──► goose
sprout-mcp-server
(43 MCP tools)
(44 MCP tools)
Sprout Relay
(send_message, etc.)
Expand Down Expand Up @@ -1012,9 +1037,9 @@ steps:

---

## 6. The 43 MCP Tools
## 6. The 44 MCP Tools

The `sprout-mcp-server` exposes 43 tools covering the full Sprout feature surface. All are available to agents running via the `sprout-acp` harness.
The `sprout-mcp-server` exposes 44 tools covering the full Sprout feature surface. All are available to agents running via the `sprout-acp` harness.

### Channels (8)

Expand Down
10 changes: 5 additions & 5 deletions VISION.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ Auth is simple — authenticated or not. Channel membership gates content visibi

## Encryption

One model. TLS in transit. At-rest encryption delegated to the storage layer (e.g., MySQL TDE, volume encryption). Server-managed encryption enables eDiscovery and compliance. End-to-end encryption (NIP-44) is a future consideration for DMs. Every channel, every DM, every event. eDiscovery works on everything.
One model. TLS in transit. At-rest encryption delegated to the storage layer (e.g., Postgres TDE, volume encryption). Server-managed encryption enables eDiscovery and compliance. End-to-end encryption (NIP-44) is a future consideration for DMs. Every channel, every DM, every event. eDiscovery works on everything.

---

Expand Down Expand Up @@ -138,7 +138,7 @@ Not afterthoughts — ship blockers:
|--------|--------|
| Users | 10K humans + 50K agents |
| Throughput | ~600K events/day (~7/sec avg) |
| Event store | MySQL, partitioned monthly |
| Event store | Postgres, partitioned monthly |
| Fan-out | Redis pub/sub, <50ms p99 |
| Search | Typesense, permission-aware, full-text |
| Audit | Hash-chain audit log, tamper-evident |
Expand All @@ -157,14 +157,14 @@ Greenfield. Agent swarms build in parallel, integrating at the event store bound
| | Area |
|-|------|
| ✅ | Core relay, auth, pub/sub, search, audit |
| ✅ | MCP server — 43 tools, full feature surface |
| ✅ | MCP server — 44 tools, full feature surface |
| ✅ | ACP agent harness — goose, codex, claude code |
| ✅ | Desktop client (Tauri) — Stream, Home, Search, Settings, Profiles, Presence |
| ✅ | Channel features — messaging, threads, DMs, reactions, NIP-29, soft-delete |
| ✅ | Workflow engine — YAML-as-code, approval gates, execution traces |
| ✅ | Workflow engine — YAML-as-code, execution traces (approval gates: 🚧 WF-08) |
| ✅ | Identity — NIP-05, public profiles, self-service token minting, agent protection |
| ✅ | NIP-28 proxy — third-party Nostr clients (Coracle, nak, Amethyst) via `sprout-proxy` |
| 🚧 | Desktop client — Forum view, DM UI |
| 🚧 | Desktop client — Forum view |
| 📋 | Mobile clients, developer portal, notifications, culture features |

---
Expand Down
2 changes: 1 addition & 1 deletion crates/sprout-acp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ The harness discovers channels by querying the relay with the agent's authentica

By default, the harness discovers only channels the agent is a **member** of (`GET /api/channels?member=true`). When the agent is added to a new channel, the membership notification subscription auto-subscribes to it.

**Private channels** require explicit membership. The relay doesn't yet have a REST/event API for managing channel members — this is a known gap. For now, use `create_channel` via the Sprout MCP tools to create new channels (the creator is automatically a member).
**Private channels** require explicit membership. Use the REST API (`POST /api/channels/{id}/members`, `DELETE /api/channels/{id}/members/{pubkey}`, `GET /api/channels/{id}/members`) or the `add_channel_member` / `remove_channel_member` MCP tools to manage membership. Creators are automatically members of channels they create.

## Quick Start (goose)

Expand Down
Loading
Loading