live · mainnetoc · docs
specs · api · guides
docs / state machine

State machine

Every pledge is in exactly one of these states at any given time:

StateMeaning
pendingSworn, before resolves_at.
resolvablePast resolves_at, within expires_at, no outcome published yet.
keptOutcome published, outcome = kept, dispute window passed.
brokenOutcome published, outcome = broken, dispute window passed.
disputedContradictory outcome envelopes within dispute window — pending dispute.
expired_unresolvedPast expires_at with no consistent outcome.

State transitions are pure functions of (pledge envelope, outcome envelope or absence, current time, relevant public state). Two verifiers with the same inputs produce the same classification. This is a conformance requirement — disagreement on state is a protocol bug.

Transitions

stateDiagram-v2
    [*] --> pending: sworn
    pending --> resolvable: now ≥ resolves_at
    pending --> broken: abandonment<br/>envelope
    resolvable --> kept: outcome verified<br/>(after dispute window)
    resolvable --> broken: outcome verified<br/>(after dispute window)
    resolvable --> disputed: contradictory<br/>outcome envelope
    resolvable --> expired_unresolved: now ≥ expires_at<br/>no outcome
    disputed --> kept: dispute mechanism resolves
    disputed --> broken: dispute mechanism resolves
    disputed --> expired_unresolved: no resolution<br/>by expires_at
    kept --> [*]
    broken --> [*]
    expired_unresolved --> [*]

    classDef good fill:#0a0a0a,stroke:#16a34a,stroke-width:2px,color:#fafafa
    classDef bad fill:#0a0a0a,stroke:#dc2626,stroke-width:2px,color:#fafafa
    classDef neutral fill:#0a0a0a,stroke:#71717a,stroke-width:2px,color:#fafafa
    class kept good
    class broken,disputed bad
    class expired_unresolved neutral

Abandonment counts as broken — there is no separate abandoned state. Refusing the "honorable exit" classification preempts the race-to-abandon attack where swearers dodge a foreseeable break by admitting it slightly earlier.

What "verified" means in resolvable → kept|broken

For deterministic mechanisms (chain_state, nostr_event_exists, stamp_published, http_get_hash, dns_record): the verifier re-computes the outcome envelope from public state. Byte-equal to the published one ⇒ verified.

For counterparty_signs and vote_resolves: BIP-322 signature checks under the resolver address; for vote_resolves, the named poll must have a finalized tally meeting the pledge's threshold.

Bond verification at every transition

A pledge with a bond reference whose underlying OC Attest UTXO is spent mid-pledge is not auto-reclassified by the protocol — that's a verifier-policy decision (see SPEC §8). The protocol surfaces E_BOND_SPENT as an error code; an integrator choosing strict policy treats a spent bond as an automatic break, while a lenient one treats it as informational. Refusing automatic reclassification preserves the no-slashing rule — the protocol never does anything to a swearer's status; it only exposes the facts.

Dispute window

For deterministic outcomes the dispute window is short (default 24 hours) because contradiction is unlikely — if two observers reach different outcomes for the same public state, at least one is buggy. For counterparty_signs it's longer (default 7 days) because contradiction is the expected disagreement mode and the pledge's dispute.mechanism needs time to resolve.

The dispute window length is a per-pledge field, not a global default. Swearers choose at swearing time.

The expired_unresolved class is intentional

A pledge with no outcome by expires_at is not automatically broken. It's a third outcome class, queryable separately. The reason: a pledge with counterparty_signs resolution where the counterparty just never shows up is informational about the counterparty's behavior, not the swearer's. Conflating it with broken would let lazy counterparties break pledges by silence.

Verifiers building gates can compose any policy they want — count expired_unresolved as broken for one address class, ignore it for another. The protocol surfaces the raw fact.