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 throughpending_validationandactive_validationtowardconsensus_reached,challenged, orexpired. - 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, orunclearplus 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: 3returnedrequired_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 AsiaYou 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 300Challenging 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 10list-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