This document is the dense reference companion to the public guide Disputes and evidence evaluation. It describes how automated predicate evaluation differs from human dispute workflows, maps Harbor and Gateway state machines, and documents the arbitration handoff packet.
Design principles
- Evidence evaluation is deterministic. Harbor runs
paybond-predicate-vmover signed payee evidence and the intent's frozen predicate contract. The result is stored aspredicate_passedbefore any terminal money move. - Predicate failure is not an HTTP error.
submit_evidencereturns200withpredicate_passed: false;settlement/confirmdrives refund when appropriate. - Disputes freeze automation. Harbor
disputedblocksdrive_terminal_settlementuntildispute/resolve. Gateway cases add timeline, notes, evidence URI refs, and export — they do not replace Harbor money authority forresolved_release/resolved_refund. - Provider chargebacks are signals. Stripe
charge.dispute.*ingest may setsettlement_exception_*fields and SignalPROVIDER_STRIPE_DISPUTEwithout transitioning intent state. - Tenant isolation at every hop. Gateway dispute queries filter by authenticated
tenant_id. Harbor operator routes require tenant-scoped upstream bearer or mTLS client identity.
Evidence evaluation
sequenceDiagram
autonumber
actor Payee as Payee / Kit
participant GW as Gateway
participant HB as Harbor
participant VM as Predicate VM
participant Rail as Settlement rail
Payee->>GW: POST /harbor/intents/{id}/evidence (tenant API key)
GW->>HB: Forward evidence (mTLS)
HB->>HB: Verify EvidenceSignV1 (version 2)
HB->>VM: Evaluate predicate_dsl or policy_binding
HB->>HB: Optional completion_schema drift report
HB->>HB: funded → evidence_submitted
HB-->>GW: predicate_passed, predicate_evaluation, schema_validation
GW-->>Payee: 200 JSON body
Payee->>GW: POST /harbor/intents/{id}/settlement/confirm
GW->>HB: Forward confirm
alt predicate_passed = true
HB->>Rail: Capture / release
HB-->>HB: released
else predicate_passed = false
HB->>Rail: Cancel / refund
HB-->>HB: refunded
endEvidence contract inputs
| Input | Source | Notes |
|---|---|---|
predicate_dsl | Intent create (signing v6) | Inline JSON predicate document |
policy_binding | Intent create (signing v7) | Managed policy head + digest |
completion_preset_id | Intent create | Optional; catalog frozen at create |
| Payee evidence payload | POST /evidence body | Must match evidence schema expectations |
EvidenceSignV1 | Payee key | version = 2, bincode 2 wire |
Kit signing helpers: kit/ts/src/agent/evidence.ts, Python parity in paybond_kit.
Predicate evaluation response (Harbor)
Key fields on successful evidence submit:
| Field | Meaning |
|---|---|
predicate_passed | true / false — business outcome |
predicate_evaluation | Structured VM report (rule trace) |
schema_validation | Drift report when preset bound; signal-only in v1 |
state | evidence_submitted after first successful bind |
Ledger events appended include EvidenceSubmitted, PredicateEvaluated, and optionally CompletionSchemaValidated.
Schema validation (signal-only)
completion_schema.rs validates vendor and canonical shapes at submit time. Failures appear in schema_validation but do not reject the HTTP request. Operators may see passed_with_drift when the predicate passed but quality or vendor checks failed.
One-shot evidence binding
Lifecycle transition funded → evidence_submitted accepts exactly one evidence submission path per intent. Re-submission is not supported; contested outcomes use disputes.
Dispute handling
sequenceDiagram
autonumber
actor Op as Operator
participant GW as Gateway
participant PG as Postgres cases
participant HB as Harbor
participant Rail as Settlement rail
Op->>GW: POST /v1/disputes/cases { intent_id, subject, ... }
GW->>HB: POST /operator/v1/intents/{id}/dispute/open
HB-->>HB: state → disputed
GW->>PG: Insert case (status frozen), timeline
GW-->>Op: 201 { case, harbor }
Op->>GW: POST /v1/disputes/cases/{id}/notes
Op->>GW: POST /v1/disputes/cases/{id}/evidence { label, ref_uri }
GW->>PG: Append timeline / evidence refs
Op->>GW: POST /v1/disputes/cases/{id}/resolve { resolution }
alt resolved_release
GW->>HB: POST .../dispute/resolve { action: release }
HB->>Rail: Capture / release
HB-->>HB: released
GW->>PG: status resolved_release
else resolved_refund
GW->>HB: POST .../dispute/resolve { action: refund }
HB->>Rail: Cancel / refund
HB-->>HB: refunded
GW->>PG: status resolved_refund
else resolved_split or escalated_external
GW->>PG: Case status only (no Harbor resolve)
endHarbor intent state machine (dispute subset)
stateDiagram-v2
[*] --> funded
funded --> evidence_submitted: submit evidence
funded --> disputed: dispute/open
evidence_submitted --> disputed: dispute/open
evidence_submitted --> released: confirm + passed
evidence_submitted --> refunded: confirm + failed
disputed --> released: dispute/resolve release
disputed --> refunded: dispute/resolve refund
released --> [*]
refunded --> [*]Terminal states released and refunded are not re-opened by provider webhooks alone. Late Stripe dispute events on ACH may set settlement_exception_* while state remains terminal — see chargeback section below.
Gateway dispute case statuses
Case status | Harbor effect | Typical use |
|---|---|---|
open | None | Settlement-only case (settlement_object_ref, no intent_id) |
frozen | Intent already disputed via combined create | Active review |
resolved_release | Harbor released via resolve | Operator approves payee after freeze |
resolved_refund | Harbor refunded via resolve | Operator sides with principal |
resolved_split | None | Off-rail split; manual money movement outside Harbor partial capture |
escalated_external | None | External arbitration; export packet |
Resolve API accepts resolution values: resolved_release, resolved_refund, resolved_split, escalated_external (aliases release / refund also accepted for Harbor-backed resolutions).
Link path (Harbor opened first)
POST /v1/disputes/cases/link-disputed-intent verifies Harbor state = disputed, then inserts a case without calling dispute/open again. Returns existing case if one is already registered for the intent.
Implementation: disputes.go.
Stripe chargeback vs Harbor dispute
sequenceDiagram
participant Stripe
participant GW as Gateway webhooks
participant HB as Harbor ingest
participant Sig as Signal
Stripe->>GW: charge.dispute.created
GW->>HB: POST /internal/v1/stripe/events
HB->>HB: Match intent via metadata.paybond_intent_id
HB->>HB: settlement_exception manual_review (intent state unchanged)
HB-->>Sig: PROVIDER_STRIPE_DISPUTE observation (optional)
Note over HB: Operator must explicitly dispute/open for freezeACH ingest sets exception code stripe_ach_charge_dispute (stripe_sync.rs). Signal maps charge.dispute.* to PROVIDER_STRIPE_DISPUTE (fraud_signals.go).
| Signal | Harbor intent state | Gateway case | Settlement frozen |
|---|---|---|---|
Predicate passed: false | Moves to refunded on confirm | Optional | No — automated refund path |
Operator dispute/open | disputed | Created via cases API | Yes |
Stripe charge.dispute.* | Unchanged | Not auto-created | No |
HTTP surface
Evidence and settlement (tenant API via Gateway)
| Method | Path | Role |
|---|---|---|
POST | /harbor/intents/{id}/evidence | Submit signed evidence; predicate evaluation |
POST | /harbor/intents/{id}/settlement/confirm | Terminal release or refund from stored evaluation |
Harbor operator (direct or Gateway proxy)
| Method | Path | Body | Effect |
|---|---|---|---|
POST | /operator/v1/intents/{id}/dispute/open | { summary, reason_code } | disputed, settlement frozen |
POST | /operator/v1/intents/{id}/dispute/resolve | { action: release | refund } | Terminal money move from disputed |
GET | /operator/v1/intents/{id} | — | Timeline, exceptions, provider fields |
Gateway proxy prefix: /harbor/operator/v1/intents/{id}/dispute/...
Gateway dispute cases
Requires CanManageDisputes RBAC and Private dashboards commercial feature.
| Method | Path | Role |
|---|---|---|
POST | /v1/disputes/cases | Open case (+ Harbor freeze when intent_id set) |
POST | /v1/disputes/cases/link-disputed-intent | Link case to existing Harbor dispute |
GET | /v1/disputes/cases | List (filters: status, intent_id, pagination) |
GET | /v1/disputes/cases/{case_id} | Detail + timeline + evidence refs |
POST | /v1/disputes/cases/{case_id}/resolve | Resolve (Harbor-backed or gateway-only) |
POST | /v1/disputes/cases/{case_id}/assign | Assignee user |
POST | /v1/disputes/cases/{case_id}/evidence | Attach { label, ref_uri } |
POST | /v1/disputes/cases/{case_id}/notes | { body, visibility: tenant | internal } |
GET | /v1/disputes/cases/{case_id}/export/arbitration | Arbitration handoff JSON |
Schemas: Gateway OpenAPI.
ArbitrationHandoffPacketV1
Stable export shape for third-party reviewers (PAYBOND-V1-005):
{ "packet_version": "v1", "tenant_realm": "550e8400-e29b-41d4-a716-446655440000", "case": { }, "timeline": [ { "seq": 1, "event_type": "case_opened", "created_at": "2026-07-02T13:00:00Z", "payload": { } } ], "evidence_refs": [ { "label": "vendor_ticket", "ref_uri": "https://support.example/ticket/123" } ], "content_sha256_hex": "…" }
| Field | Description |
|---|---|
packet_version | Always v1 today |
tenant_realm | Authenticated tenant realm id |
case | Full paybond_dispute_case row |
timeline | Non-internal rows by default; include_internal=1 requires tenant admin or audited support |
evidence_refs | URI references attached to the case (not Harbor evidence payloads) |
content_sha256_hex | SHA-256 of canonical JSON with this field empty before final serialization |
Defined in ArbitrationHandoffPacketV1.
Decision reference
| Situation | Recommended path |
|---|---|
| Evidence clearly fails declared rule | Accept predicate_passed: false → settlement/confirm → refunded |
| Evidence passes rule; partner disagrees | dispute/open before or instead of confirm; human resolve |
| Ambiguous fulfillment | Dispute freeze; gather case evidence refs; resolve or escalate |
| Stripe chargeback after release | Review settlement_exception_*; open dispute only if freeze/action needed |
| Partial settlement off-rail | resolved_split (no Harbor partial capture) + manual provider action |
| External arbitrator | escalated_external → GET .../export/arbitration |
Kit and Signal boundaries
| Surface | Evidence | Disputes |
|---|---|---|
| Kit | submitEvidence, middleware auto-evidence | Not exposed — Gateway HTTP / console only |
| Signal | Lifecycle outcomes from Harbor terminal events | PROVIDER_STRIPE_DISPUTE and disputed outcome counters |
See support matrix.
Related
- Disputes and evidence evaluation — adopter-facing guide with curl examples
- Intent lifecycle — full state diagram
- Evidence & artifacts — Kit signing checklist
- Completion presets — preset-bound predicates
- Harbor predicate DSL — rule language reference
- Operations runbook §2 — dispute SLAs and RBAC
- Settlement provider integration — terminal capture per rail