Skip to content

feat: add vouch and report API endpoints with NIP-98 auth#7

Open
CodyTseng wants to merge 4 commits into
masterfrom
feat/vouch-report-api
Open

feat: add vouch and report API endpoints with NIP-98 auth#7
CodyTseng wants to merge 4 commits into
masterfrom
feat/vouch-report-api

Conversation

@CodyTseng
Copy link
Copy Markdown
Owner

Summary

  • New POST /vouch and POST /report endpoints (NIP-98 authenticated, gated by vouch.enabled) let users contribute to the reputation graph without actually following each other — solving the "I know this new account isn't spam but I don't want to follow them" cold-start problem.
  • Mutual-exclusion toggle: posting one side atomically deletes any existing row from the other side for the same (source, target) pair. There are no DELETE endpoints — reversing a stance requires posting the opposite stance.
  • Graph integration: vouches become equal-weight follow edges, deduped against real follows from the same source (a user who follows and vouches for the same target contributes one edge, not two). Reports apply a trust-weighted penalty to the target's final score: final = raw * (1 - R / (R + F)) where R is the summed trust of trusted reporters and F is the summed trust of the target's incoming edges.
  • Silent admission: submissions from pubkeys with no last-round TrustRank and not in seed_pubkeys return 200 but are not persisted (prevents Sybil inflation while keeping the client unable to probe the admission oracle).
  • API now opens the SQLite DB in ModeReadWrite; WAL mode + the existing writeMu already serialise writes safely across the crawler and API processes. Migration v4 adds the vouches and reports tables.

Test plan

  • go test ./... — 26 new tests pass (11 repository, 11 NIP-98 middleware, 4 ranking)
  • go vet ./... clean
  • go build ./cmd/api and go build ./cmd/crawler produce binaries
  • End-to-end with a real NIP-98 client (curl + signed event) against a dev instance with vouch.enabled: true
  • Verify docker-compose build still succeeds
  • Confirm migration v4 runs cleanly against a pre-existing DB from the previous schema

🤖 Generated with Claude Code

codytseng and others added 4 commits April 23, 2026 16:32
Two new POST endpoints (gated by vouch.enabled config) let users submit
signed claims directly instead of through a Nostr event. Vouches act as
equal-weight follow edges in the ranking graph, deduped against any
existing follow from the same source. Reports apply a trust-weighted
penalty to the target's final score: final = raw * (1 - R/(R+F)).

POST /vouch and POST /report are mutually exclusive per (source, target):
posting one atomically removes the opposite side, so there is no DELETE
endpoint — toggling sides is the only way to retract. Submissions from
pubkeys with no TrustRank (and not in seed_pubkeys) return 200 but are
silently dropped to prevent spam-account inflation.

The API now opens the DB in read-write mode; WAL + writeMu already
coordinate it with the crawler. Migration v4 adds vouches and reports
tables. 26 new tests cover NIP-98 middleware, repository mutex/toggle
behaviour, and ranking integration (vouch promotes unfollowed users,
reports decay scores, untrusted reporters are ignored, follow+vouch
edges dedupe).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both API and crawler now open the database read-write, so the
ModeReadOnly branch was dead code. Collapse to a single New(path)
entry point. The API previously inherited the write-mode pool size
of 1, which would have serialized its reads; restore a 10-connection
pool for all callers. Writes are still serialized by writeMu and
SQLite's own locks, so extra connections do not cause contention.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A vouch is a weaker signal than an actual follow: the user is asserting
non-spam without committing to see the target's posts. Carry a per-edge
weight through the graph so vouch-only edges contribute proportionally
less flow than a full follow.

Config: vouch.weight (default 0.5, range (0, 1]).

PageRank and TrustRank inner loops now divide by outWeight (sum of
outgoing edge weights) instead of outDegree (count). Each in-edge
carries its own weight; a source's score flows to each target in
proportion to weight / outWeight. Follow and vouch edges share the
same adjacency list — only their weights differ. outDegree is still
tracked as an int so the pubkeys table's Following column remains a
count. New test verifies that lowering the weight produces a lower
score for a vouch-only recipient.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Having both fields required the user to keep them consistent: enabling
with weight=0 (or disabling with weight=0.5) both produced inconsistent
states. Use weight alone — 0 means off (endpoints return 404 and the
ranking calculator skips streaming vouches entirely), > 0 means on and
is the edge weight. Default 0 preserves prior off-by-default behaviour.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant