ServicesConsensus

Consensus

A reputation-weighted truth registry: anyone can propose a fact as a statement with evidence, registered validators attest with a confirm/deny/unclear vote, and a BFT-style quorum - minimum validator count, agreement threshold, geographic diversity - decides the verdict, which can then be disputed through staked, admin-reviewed challenges.

When to reach for it: community fact-checking with transparent vote trails, oracle resolution for Pact prediction markets, verifying user-reported game outcomes, attesting real-world events.

When not to: a simple vote among options with no validator identity or weighting belongs to Poll; flag-and-review moderation of user content belongs to Comment.

Concepts

  • Facts - a statement, a category (like news_event), and optional evidence, moving through pending_validation and active_validation toward consensus_reached, challenged, or expired.
  • Validators - registration is self-service and the validator ID is your JWT subject: one validator per identity, starting at reputation 0.5 with a declared stake balance.
  • Attestations - confirm, deny, or unclear plus a confidence between 0.0 and 1.0. One vote per validator per fact - there is no re-vote - and reputation, stake, and a geographic bonus multiply into each vote's weight (How a verdict forms).
  • Consensus requirements are per-fact, falling back to server defaults. The validator minimum is floored at the server minimum (10 by default): proposing with minimum_validators: 3 returned required_validators: 10. Geographic minimums may go below the defaults.
  • Challenges - a staked dispute that freezes voting until an admin upholds or rejects it, settling the stake and every voter's reputation.

Propose a fact

curl -X POST -H "Authorization: Bearer $SNUG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "statement": "Earthquake of magnitude 7.2 occurred in Tokyo at 14:30 UTC",
    "category": "news_event",
    "evidence": [{"type": "url", "value": "https://example.com/quake-report"}],
    "consensus_requirements": {"minimum_countries": 1, "minimum_continents": 1, "time_limit_seconds": 1800}
  }' http://localhost:4000/api/v1/consensus/facts
{
  "status": 201,
  "msg": "Created",
  "data": {
    "fact_id": "XfUWycRMKsFBNweEGequ",
    "status": "pending_validation",
    "statement": "Earthquake of magnitude 7.2 occurred in Tokyo at 14:30 UTC",
    "consensus_deadline": "2026-08-28T07:00:31.223944Z",
    "current_validators": 0,
    "required_validators": 10
  }
}

The CLI equivalent is snug consensus propose-fact with --min-validators, --min-countries, --confidence-threshold, and similar flags (evidence is HTTP-only).

Register and attest

snug consensus register-validator --country Japan --continent Asia \
  --specialization news_event --stake 1000
snug consensus attest --fact-id XfUWycRMKsFBNweEGequ \
  --vote confirm --confidence 0.9 --stake 200 --country Japan --continent Asia

You always attest as yourself, and the response echoes your computed weight (0.9 here, from reputation 0.5 x geo bonus 1.5 x stake multiplier 1.2) plus a running consensus_update - after this first vote the tally already read likely_true. Reproduced failure modes: registering twice is 409 validator_already_exists, an unregistered caller attesting gets 403 validator_not_registered, a second vote on the same fact 409 duplicate_attestation, a confidence outside 0.0-1.0 a 400. The stake boosts weight without being debited (overdrawing is a 400), and location is per-attestation: an otherwise-identical vote (reputation 0.5, stake 200) scored 0.9 with a location and 0.6 without one, and unknown locations do not count toward the diversity quorum.

Track the verdict

snug consensus status --fact-id ... returns the live tally. Captured right after the tenth confirming validator pushed the fact over quorum:

{
  "status": "consensus_reached",
  "consensus_status": {
    "phase": "commit",
    "confidence": 1.0,
    "determination": "true",
    "attestations": { "total": 10, "confirm": 10, "deny": 0, "unclear": 0 },
    "weighted_votes": { "confirm": 8.325, "deny": 0.0, "unclear": 0.0 }
  },
  "geographic_distribution": { "countries": 9, "continents": 6, "distribution_score": 1.0 }
}

One attestation earlier the same fact read active_validation / pre_commit / likely_true: threshold met, quorum not - the full ladder is on How a verdict forms. Deadlines are enforced lazily: once consensus_deadline passes, the next read flips an unresolved fact to expired and attesting it fails with 409 fact_expired.

Challenge and resolve

snug consensus challenge --fact-id XfUWycRMKsFBNweEGequ \
  --reason new_evidence --stake 300

Challenging flips the fact to challenged and freezes voting - further attestations fail with 409 consensus_closed. A challenger who is a registered validator has the stake debited from their balance (verified: 1000 dropped to 700; overdrawing is a 400).

Resolution requires a platform admin token - other callers get 403 insufficient_permissions, and resolving twice 409 challenge_already_resolved. Upholding, captured live:

snug consensus resolve-challenge --challenge-id UJtgNRHvGYHPjTpptPZq \
  --decision uphold --notes "new evidence is credible"
{
  "status": "upheld",
  "fact_status": "consensus_reached",
  "determination": "disputed",
  "stake_returned": 300,
  "validators_rewarded": 0,
  "validators_slashed": 10
}

Rejecting instead forfeits the stake and rewards the confirming voters - both branches are detailed on How a verdict forms.

Real-time stream

GET /api/v1/ws/consensus/{topic} upgrades to a WebSocket following one fact (the topic is the fact ID); browser clients may pass the JWT as ?token= instead of the Authorization header. The CLI wraps it:

snug consensus stream --topic XfUWycRMKsFBNweEGequ --raw
{"type":"attestation_cast","fact_id":"XfUWycRMKsFBNweEGequ","validator_id":"docs4-v2","vote":"confirm","determination":"true","total_attestations":10}
{"type":"challenge_resolved","fact_id":"XfUWycRMKsFBNweEGequ","challenge_id":"UJtgNRHvGYHPjTpptPZq","status":"upheld","determination":"disputed","fact_status":"consensus_reached"}

The stream is read-only; both frame shapes are in the API reference.

Finding facts

GET /api/v1/consensus/facts uses the shared search grammar: q full-text searches the statement, filter takes field:operator:value expressions, and sort_by accepts created_at or consensus_deadline:

snug consensus list-facts -q "earthquake" --filter category:eq:news_event \
  --sort-by created_at --sort-order desc --page-size 10

list-validators filters on min_reputation and country instead.

Limits and configuration

Statements up to 4000 characters, 50 evidence items per fact, a 0.67 default agreement threshold, and a one-hour default consensus window - all tunable, along with the validator floor, geographic minimums, and the reputation reward/slash economics, via the CONSENSUS_* variables in the Consensus CONFIG reference.

Reference

  • Consensus API - every endpoint, callable
  • How a verdict forms - vote weighting, the determination ladder, challenge economics
  • Related: Poll for plain voting, Reputation for the scoring engine behind validator accuracy, Pact for prediction markets that need an oracle

On this page