ServicesPact

Resolution, Disputes, and Settlement

Three distinct moments, in order: resolve records a verdict, the dispute window lets anyone challenge it, and settlement actually moves the escrowed money and stamps a receipt. Conflating the first and last is the most common pact mistake - a resolved pact has paid nobody yet.

Resolving

Only the creator or a platform admin may resolve, and only from open, claimed, or locked; anyone else gets 403 insufficient_permissions. Markets name a winning --outcome; the other kinds pass --fulfilled (present = kept / claim accepted / covered event happened, absent = failed):

snug pact resolve --pact-id pact_b95d... --fulfilled --evidence "strava screenshots attached"
{
  "status": "resolved",
  "receipt": null,
  "resolution": {
    "fulfilled": true,
    "resolved_by": "docs-wave",
    "verifier": "manual",
    "detail": "strava screenshots attached",
    "resolved_at": "2026-08-28T14:16:59.675338Z",
    "dispute_window_ends_at": "2026-08-29T14:16:59.675338Z"
  }
}

Note receipt: null - the verdict is recorded and the dispute window is open, but nothing has been paid.

Verifiers

The pact's verifier, fixed at creation, decides how much the resolver's word counts:

  • manual - the resolve call itself is the verdict.
  • arbiter - the evidence and claimed verdict are handed to the Arbiter LLM judge against the pact's --rubric (falling back to the description); an aggregate score of at least 0.5 verifies. The server must have an LLM provider configured or resolution fails with 400 resolution_failed.
  • hitl - resolution is gated on human approval. The first resolve call files a HITL request with the pact's --approver list and returns 403 verifier_mismatch: hitl decision is still pending. Once enough approvers respond, resolving again completes:
snug pact resolve --pact-id $PACT --fulfilled          # 403 verifier_mismatch, files the request
# as the approver:
snug hitl approval respond --request-id pact_${PACT}_0 --approve
# as the creator, again:
snug pact resolve --pact-id $PACT --fulfilled
# -> status: resolved, resolution.external_ref: pact_pact_a0ff..._0

A rejection (or a rejected arbiter verdict) still resolves the pact - as fulfilled: false, which routes the money accordingly.

The dispute window

While status is resolved and the window is open, anyone may file one dispute; the configured bond (100 by default) is escrowed from the challenger. After dispute_window_ends_at it is 409 dispute_window_closed, and a second dispute is 409 invalid_transition.

snug pact dispute --pact-id pact_b95d... --statement "the screenshots are from last week"

The creator or an admin judges it:

snug pact resolve-dispute --pact-id pact_b95d... --uphold   # omit --uphold to dismiss
  • Upheld - the resolution rewinds: status back to open, resolution and claimant cleared, bond returned, and the finished dispute is archived in metadata.dispute_history. The pact can be resolved again from scratch.
  • Dismissed - the bond forfeits to the creator and the pact settles immediately.

Including the rewind leaving claimant: null and one dispute_history entry.

When settlement runs

Settlement fires in exactly two places: the background sweeper settles each resolved pact once its dispute window closes, and a dismissed dispute settles on the spot. The sweeper (which also reopens lapsed bounty claims) is off by default - without PACT_SWEEPER_ENABLED=true a resolved pact whose window expires undisputed stays resolved forever and its escrow is stranded, so enable it anywhere pacts are real.

Receipts

Settlement writes a SettlementReceipt onto the pact: escrowed and distributed totals, per-beneficiary legs, Treasury transfer ids, and a SHA-256 receipt hash. A settled market, captured live (600 on the winning outcome, 400 against, 5% creator fee):

{
  "escrowed_total": 1000,
  "distributed_total": 1000,
  "dust": 0,
  "legs": [
    { "beneficiary_id": "docs4-other", "category": "payout", "amount": 980 },
    { "beneficiary_id": "docs-wave", "category": "creator_fee", "amount": 20 }
  ],
  "receipt_hash": "sha256:d8f1a64b47c5...",
  "transfer_ids": ["txn_0527a7f7c7bd...", "txn_8929b889ac8a..."]
}

Where the money goes, per kind:

  • Oath - fulfilled returns the stake (return); failed routes it to the forfeit destination (slash, e.g. to burn_sink).
  • Bounty - fulfilled pays the claimant (payout); failed or unclaimed refunds the creator (refund).
  • Market - winners get their stake back plus a pro-rata share of the losing pool after the creator fee; if nobody backed the winner, all positions are refunded (void_refund). Integer division remainders (dust) go to the first winner, and the receipt is rejected outright if legs stop summing to the pool.
  • Pool - fulfilled pays pool_payout_amount (capped at the reserve) to the beneficiary and refunds the remainder to contributors pro-rata; unfulfilled refunds everything pro-rata.

Settled market positions show their final payout in snug pact positions (settled: true, payout: 980 for the example above).

Voiding

snug pact void unwinds an open, claimed, or locked pact: every stake, position, and contribution is refunded and the status becomes voided. A settled pact cannot be voided (409 invalid_transition: pact in Settled cannot be voided), and a voided pact has no receipt - refunds are plain transfers, not a settlement.

On this page