# dev.cocore.compute.dispute

> Published by [cocore.dev](https://lexicon.garden/identity/did:plc:5quuhkmwe2q4k3azfsgg7kdz)

✓ This is the authoritative definition for this NSID.

## Description

An exchange-signed adjudication of a complaint about a settled receipt. Published in the exchange's repo. The exchange's DID is the only DID permitted to publish a record at this NSID for a given settlement; any record at this NSID outside the exchange's repo is invalid by construction. The dispute lifecycle is open->resolved (terminal); resolution carries the outcome the exchange has already enacted (e.g. a refund settlement) and a signed rationale. Verifiers reading a settlement see status=disputed when an open dispute exists, and a paired settlement of status=refunded with `refundOf` pointing at the original when the outcome warranted a refund.

## Links

- [View on Lexicon Garden](https://lexicon.garden/lexicon/did:plc:5quuhkmwe2q4k3azfsgg7kdz/dev.cocore.compute.dispute)
- [Documentation](https://lexicon.garden/lexicon/did:plc:5quuhkmwe2q4k3azfsgg7kdz/dev.cocore.compute.dispute/docs)
- [Examples](https://lexicon.garden/lexicon/did:plc:5quuhkmwe2q4k3azfsgg7kdz/dev.cocore.compute.dispute/examples)

## Definitions

### `dev.cocore.compute.dispute`

**Type**: `record`

**Key**: `tid`

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `sig` | `string` | No | ES256 signature (base64url, no padding) over the canonical JSON of every other field. Verified against the same verificationMethod that signs settlements for this exchange. Required for trust-tier=hardware-attested exchanges; optional in v0.3.x as we roll out signing across all records. |
| `reason` | `ref` → `#disputeReason` | Yes | Why the dispute was raised. Free-form rationale plus an enum bucket for matchmaking / analytics. |
| `status` | `string` | Yes | Lifecycle state. The exchange opens the dispute (`open`) when intake is complete; once adjudicated, the same record is updated in place to `resolved` with outcome populated. Records at this NSID with status=open and no `outcome` are valid by construction; status=resolved without `outcome` is invalid. |
| `outcome` | `ref` → `#disputeOutcome` | No | Required when status=resolved. Captures the verdict and any compensating settlement record. |
| `exchange` | `string` (did) | Yes | Exchange DID. MUST equal the repo this record is published in. Adjudication is the exchange's prerogative; only the exchange that signed the settlement may sign its dispute. |
| `raisedAt` | `string` (datetime) | Yes | When the complaint was first received. May predate `createdAt` (this record is published when the exchange opens or resolves the case, which can be after intake). |
| `raisedBy` | `string` (did) | Yes | DID of the party who raised the complaint. Typically the requester (charge dispute) or the provider (non-payment claim). The exchange itself MAY raise a dispute when it detects a problem (e.g. processor chargeback fired before the requester reached out). |
| `createdAt` | `string` (datetime) | Yes |  |
| `settlement` | `ref` → `com.atproto.repo.strongRef` | Yes | Strong-ref to the dev.cocore.compute.settlement under dispute. Verifiers MUST resolve this to confirm both sides reference the same charge. |
| `evidenceCid` | `string` (cid) | No | Optional CID of the encrypted evidence bundle the exchange relied on (request/response logs, customer correspondence, processor chargeback metadata). Encrypted to the exchange + parties; opaque to public verifiers. |

### `dev.cocore.compute.dispute#disputeReason`

**Type**: `object`

Bucketed reason plus free-form detail. Buckets exist so the AppView can index disputes; detail is for the operator's own audit.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `detail` | `string` | No | Operator-supplied free-form detail. Public; do not include personally identifying information. |
| `category` | `string` | Yes | Bucket the dispute fits into. `processor-chargeback` is reserved for cases where the card network fired the chargeback ahead of any direct complaint. |

### `dev.cocore.compute.dispute#disputeOutcome`

**Type**: `object`

Exchange's verdict and any side-effects. The compensating refund (if any) is published as its own dev.cocore.compute.settlement with status=refunded and refundOf pointing at the original; this object strong-refs that record so verifiers don't need to scan.

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `verdict` | `string` | Yes | What happened. `refund-full` and `refund-partial` reverse value to the requester; `uphold-charge` keeps the original settlement intact; `forfeit-payout` keeps the requester's funds with the exchange but withholds the provider payout. |
| `decidedAt` | `string` (datetime) | Yes |  |
| `rationale` | `string` | No | Exchange's plain-English explanation. Public; bind it carefully — this is the audit trail when the same parties dispute future charges. |
| `refundSettlement` | `ref` → `com.atproto.repo.strongRef` | No | When verdict is refund-*, strong-ref to the dev.cocore.compute.settlement record (status=refunded) the exchange published as the compensating action. Required when the verdict involves a refund. |

## Raw Schema

```json
{
  "id": "dev.cocore.compute.dispute",
  "defs": {
    "main": {
      "key": "tid",
      "type": "record",
      "record": {
        "type": "object",
        "required": [
          "settlement",
          "exchange",
          "raisedBy",
          "raisedAt",
          "reason",
          "status",
          "createdAt"
        ],
        "properties": {
          "sig": {
            "type": "string",
            "maxLength": 256,
            "description": "ES256 signature (base64url, no padding) over the canonical JSON of every other field. Verified against the same verificationMethod that signs settlements for this exchange. Required for trust-tier=hardware-attested exchanges; optional in v0.3.x as we roll out signing across all records."
          },
          "reason": {
            "ref": "#disputeReason",
            "type": "ref",
            "description": "Why the dispute was raised. Free-form rationale plus an enum bucket for matchmaking / analytics."
          },
          "status": {
            "type": "string",
            "description": "Lifecycle state. The exchange opens the dispute (`open`) when intake is complete; once adjudicated, the same record is updated in place to `resolved` with outcome populated. Records at this NSID with status=open and no `outcome` are valid by construction; status=resolved without `outcome` is invalid.",
            "knownValues": [
              "open",
              "resolved"
            ]
          },
          "outcome": {
            "ref": "#disputeOutcome",
            "type": "ref",
            "description": "Required when status=resolved. Captures the verdict and any compensating settlement record."
          },
          "exchange": {
            "type": "string",
            "format": "did",
            "description": "Exchange DID. MUST equal the repo this record is published in. Adjudication is the exchange's prerogative; only the exchange that signed the settlement may sign its dispute."
          },
          "raisedAt": {
            "type": "string",
            "format": "datetime",
            "description": "When the complaint was first received. May predate `createdAt` (this record is published when the exchange opens or resolves the case, which can be after intake)."
          },
          "raisedBy": {
            "type": "string",
            "format": "did",
            "description": "DID of the party who raised the complaint. Typically the requester (charge dispute) or the provider (non-payment claim). The exchange itself MAY raise a dispute when it detects a problem (e.g. processor chargeback fired before the requester reached out)."
          },
          "createdAt": {
            "type": "string",
            "format": "datetime"
          },
          "settlement": {
            "ref": "com.atproto.repo.strongRef",
            "type": "ref",
            "description": "Strong-ref to the dev.cocore.compute.settlement under dispute. Verifiers MUST resolve this to confirm both sides reference the same charge."
          },
          "evidenceCid": {
            "type": "string",
            "format": "cid",
            "description": "Optional CID of the encrypted evidence bundle the exchange relied on (request/response logs, customer correspondence, processor chargeback metadata). Encrypted to the exchange + parties; opaque to public verifiers."
          }
        }
      }
    },
    "disputeReason": {
      "type": "object",
      "required": [
        "category"
      ],
      "properties": {
        "detail": {
          "type": "string",
          "maxLength": 2048,
          "description": "Operator-supplied free-form detail. Public; do not include personally identifying information."
        },
        "category": {
          "type": "string",
          "description": "Bucket the dispute fits into. `processor-chargeback` is reserved for cases where the card network fired the chargeback ahead of any direct complaint.",
          "knownValues": [
            "fraud",
            "non-delivery",
            "quality-failure",
            "processor-chargeback",
            "duplicate-charge",
            "other"
          ]
        }
      },
      "description": "Bucketed reason plus free-form detail. Buckets exist so the AppView can index disputes; detail is for the operator's own audit."
    },
    "disputeOutcome": {
      "type": "object",
      "required": [
        "verdict",
        "decidedAt"
      ],
      "properties": {
        "verdict": {
          "type": "string",
          "description": "What happened. `refund-full` and `refund-partial` reverse value to the requester; `uphold-charge` keeps the original settlement intact; `forfeit-payout` keeps the requester's funds with the exchange but withholds the provider payout.",
          "knownValues": [
            "refund-full",
            "refund-partial",
            "uphold-charge",
            "forfeit-payout"
          ]
        },
        "decidedAt": {
          "type": "string",
          "format": "datetime"
        },
        "rationale": {
          "type": "string",
          "maxLength": 2048,
          "description": "Exchange's plain-English explanation. Public; bind it carefully — this is the audit trail when the same parties dispute future charges."
        },
        "refundSettlement": {
          "ref": "com.atproto.repo.strongRef",
          "type": "ref",
          "description": "When verdict is refund-*, strong-ref to the dev.cocore.compute.settlement record (status=refunded) the exchange published as the compensating action. Required when the verdict involves a refund."
        }
      },
      "description": "Exchange's verdict and any side-effects. The compensating refund (if any) is published as its own dev.cocore.compute.settlement with status=refunded and refundOf pointing at the original; this object strong-refs that record so verifiers don't need to scan."
    }
  },
  "$type": "com.atproto.lexicon.schema",
  "lexicon": 1,
  "description": "An exchange-signed adjudication of a complaint about a settled receipt. Published in the exchange's repo. The exchange's DID is the only DID permitted to publish a record at this NSID for a given settlement; any record at this NSID outside the exchange's repo is invalid by construction. The dispute lifecycle is open->resolved (terminal); resolution carries the outcome the exchange has already enacted (e.g. a refund settlement) and a signed rationale. Verifiers reading a settlement see status=disputed when an open dispute exists, and a paired settlement of status=refunded with `refundOf` pointing at the original when the outcome warranted a refund."
}
```
