Version: 0.9.27 Status: Normative Last Updated: 2026-01-07
- Overview
- DisputeAttestation Object
- Dispute Types
- Grounds Codes
- Lifecycle State Machine
- Resolution and Remediation
- Validation Rules
- Error Taxonomy
- Security Considerations
This specification defines the DisputeAttestation type for formally contesting PEAC receipts, attributions, identity claims, or policy decisions. It provides:
- A standardized structure for dispute evidence
- A state machine for tracking dispute lifecycle
- Resolution semantics for outcomes and remediation
- Error codes for validation failures
This specification covers:
- Schema definitions for dispute attestations
- Dispute types and grounds codes
- State transition rules and invariants
- Resolution and remediation structures
- Contact methods for dispute communication
- Supporting evidence references
| Term | Definition |
|---|---|
| Dispute | Formal contestation of a PEAC claim |
| Grounds | Specific reasons supporting the dispute |
| Resolution | Outcome and decision for a resolved dispute |
| Remediation | Action taken to address an upheld dispute |
| Terminal | State requiring resolution (resolved, rejected, final) |
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 (RFC 2119, RFC 8174) when, and only when, they appear in all capitals, as shown here.
interface DisputeAttestation {
type: 'peac/dispute'; // REQUIRED: Type literal
issuer: string; // REQUIRED: Party filing the dispute
issued_at: string; // REQUIRED: RFC 3339 datetime
expires_at?: string; // OPTIONAL: RFC 3339 datetime
ref: string; // REQUIRED: Unique dispute ID (ULID format)
evidence: DisputeEvidence; // REQUIRED: Dispute evidence
}interface DisputeEvidence {
dispute_type: DisputeType; // REQUIRED
target_ref: string; // REQUIRED: jti:{id}, URL, or URN
target_type: DisputeTargetType; // REQUIRED
grounds: DisputeGrounds[]; // REQUIRED: 1-10 grounds
description: string; // REQUIRED: 1-4000 chars
state: DisputeState; // REQUIRED
contact?: DisputeContact; // OPTIONAL
supporting_receipts?: string[]; // OPTIONAL: max 50
supporting_attributions?: string[]; // OPTIONAL: max 50
supporting_documents?: DocumentRef[]; // OPTIONAL: max 20
state_changed_at?: string; // OPTIONAL: RFC 3339
state_reason?: string; // OPTIONAL: max 1000 chars
resolution?: DisputeResolution; // REQUIRED for terminal states
window_hint_days?: number; // OPTIONAL: 1-365
}| Field | Type | Required | Description |
|---|---|---|---|
type |
string | Yes | MUST be "peac/dispute" |
issuer |
string | Yes | URI of the party filing the dispute |
issued_at |
datetime | Yes | When the dispute was filed |
expires_at |
datetime | No | When the attestation expires |
ref |
string | Yes | Unique ULID identifier for this dispute |
evidence |
object | Yes | Dispute evidence and state |
The dispute_type field classifies the nature of the dispute:
| Type | Description |
|---|---|
unauthorized_access |
Content accessed without valid receipt |
attribution_missing |
Content used without attribution |
attribution_incorrect |
Attribution exists but is wrong |
receipt_invalid |
Receipt was fraudulently issued |
identity_spoofed |
Agent identity was impersonated |
purpose_mismatch |
Declared purpose doesn't match actual use |
policy_violation |
Terms/policy violated despite receipt |
other |
Catch-all (REQUIRES description >= 50 chars) |
The target_type field identifies what is being disputed:
| Type | Description |
|---|---|
receipt |
A PEAC receipt |
attribution |
An attribution attestation |
identity |
An agent identity attestation |
policy |
A policy decision or enforcement action |
The target_ref field uses these formats:
- Receipt:
jti:{receipt_id}(e.g.,jti:01H5KPT9QZA123456789VWXYZG) - Attribution: URL to the attribution attestation
- Identity: DID or URL (e.g.,
did:web:agent.example.com) - Policy: URL to policy document or decision endpoint
Disputes MUST include at least one ground. Maximum is 10 grounds per dispute.
| Code | Description |
|---|---|
missing_receipt |
No receipt exists for the access |
expired_receipt |
Receipt was expired at time of use |
forged_receipt |
Receipt signature invalid or tampered |
receipt_not_applicable |
Receipt doesn't cover the accessed resource |
| Code | Description |
|---|---|
content_not_used |
Content was not actually used as claimed |
source_misidentified |
Wrong source attributed |
usage_type_wrong |
Usage type incorrect (e.g., RAG claimed but was training) |
weight_inaccurate |
Attribution weight is incorrect |
| Code | Description |
|---|---|
agent_impersonation |
Agent ID was spoofed |
key_compromise |
Signing key was compromised |
delegation_invalid |
Delegation chain is broken or invalid |
| Code | Description |
|---|---|
purpose_exceeded |
Used beyond declared purpose |
terms_violated |
Specific terms were violated |
rate_limit_exceeded |
Exceeded rate limits |
interface DisputeGrounds {
code: DisputeGroundsCode; // REQUIRED
evidence_ref?: string; // OPTIONAL: Reference to supporting evidence
details?: string; // OPTIONAL: Additional context (max 1000 chars)
}FILED -> ACKNOWLEDGED -> UNDER_REVIEW -> RESOLVED
| | |
+-> REJECTED +-> ESCALATED +-> APPEALED
|
+-> FINAL
| State | Terminal | Description |
|---|---|---|
filed |
No | Initial state when dispute is created |
acknowledged |
No | Dispute received and assigned |
under_review |
No | Active investigation in progress |
escalated |
No | Escalated to senior review |
resolved |
Yes | Dispute has been resolved |
rejected |
Yes | Dispute was rejected |
appealed |
No | Previous decision being appealed |
final |
Yes | Final decision after appeal |
| From State | Valid Transitions |
|---|---|
filed |
acknowledged, rejected |
acknowledged |
under_review, rejected |
under_review |
resolved, escalated |
escalated |
resolved |
resolved |
appealed, final |
rejected |
appealed, final |
appealed |
under_review, final |
final |
(none - terminal) |
-
Terminal states REQUIRE resolution: When
stateisresolved,rejected, orfinal, theresolutionfield MUST be present. -
Non-terminal states MUST NOT have resolution: When
stateis NOT a terminal state, theresolutionfield MUST NOT be present. -
Transitioning to
appealedclears resolution: When transitioning from a terminal state toappealed, the previous resolution MUST be cleared. -
finalhas no outgoing transitions: Once infinalstate, no further state changes are allowed.
interface DisputeResolution {
outcome: DisputeOutcome; // REQUIRED
decided_at: string; // REQUIRED: RFC 3339 datetime
decided_by: string; // REQUIRED: Who made the decision
rationale: string; // REQUIRED: Explanation (1-4000 chars)
remediation?: Remediation; // OPTIONAL
}| Outcome | Description |
|---|---|
upheld |
Dispute was valid, in favor of filer |
dismissed |
Dispute invalid or without merit |
partially_upheld |
Some grounds upheld, others dismissed |
settled |
Parties reached agreement |
interface Remediation {
type: RemediationType; // REQUIRED
details: string; // REQUIRED: 1-4000 chars
deadline?: string; // OPTIONAL: RFC 3339 datetime
}| Type | Description |
|---|---|
attribution_corrected |
Attribution was fixed |
receipt_revoked |
Receipt was revoked |
access_restored |
Access was restored |
compensation |
Financial compensation provided |
policy_updated |
Policy was updated |
no_action |
No action required |
other |
Other remediation |
Implementations MUST validate:
- All required fields are present
typeis exactly"peac/dispute"refis a valid ULID (26 uppercase alphanumeric, Crockford Base32)- All enum values are recognized
- String lengths are within limits
- Datetime fields are valid RFC 3339
Implementations MUST enforce:
-
Other-type description: If
dispute_typeis"other",descriptionMUST be at least 50 characters. -
Terminal resolution: If
stateis terminal (resolved,rejected,final),resolutionMUST be present. -
Non-terminal no resolution: If
stateis NOT terminal,resolutionMUST NOT be present.
Beyond schema validation, implementations SHOULD check:
-
Temporal validity:
issued_atSHOULD NOT be in the future (beyond clock skew tolerance). -
Expiration: If
expires_atis present and in the past, the attestation SHOULD be rejected. -
State transition validity: State changes MUST follow the transition table.
| Field | Limit |
|---|---|
grounds |
1-10 items |
supporting_receipts |
max 50 items |
supporting_attributions |
max 50 items |
supporting_documents |
max 20 items |
description |
max 4000 chars |
ground.details |
max 1000 chars per ground |
rationale |
max 4000 chars |
remediation.details |
max 4000 chars |
| Code | Description |
|---|---|
E_DISPUTE_INVALID_FORMAT |
Schema validation failure |
E_DISPUTE_INVALID_ID |
Invalid ULID format |
E_DISPUTE_INVALID_TYPE |
Unknown dispute type |
E_DISPUTE_INVALID_TARGET_TYPE |
Unknown target type |
E_DISPUTE_INVALID_GROUNDS |
Unknown grounds code |
E_DISPUTE_INVALID_STATE |
Unknown state value |
E_DISPUTE_INVALID_TRANSITION |
Invalid state transition |
E_DISPUTE_MISSING_RESOLUTION |
Resolution required for terminal state |
E_DISPUTE_RESOLUTION_NOT_ALLOWED |
Resolution provided for non-terminal state |
E_DISPUTE_OTHER_REQUIRES_DESCRIPTION |
'other' type needs 50+ char description |
| Code | Description |
|---|---|
E_DISPUTE_NOT_YET_VALID |
issued_at is in the future (retryable) |
E_DISPUTE_EXPIRED |
Attestation has expired |
| Code | HTTP | Description |
|---|---|---|
E_DISPUTE_TARGET_NOT_FOUND |
404 | Target not found (retryable) |
E_DISPUTE_DUPLICATE |
409 | Duplicate dispute ID |
Per RFC 9110, 401 responses MUST include WWW-Authenticate:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: PEAC-Attestation realm="peac", attestation_type=dispute, error=expired
Content-Type: application/problem+json
{
"type": "https://www.peacprotocol.org/errors#E_DISPUTE_EXPIRED",
"title": "Dispute Expired",
"status": 401,
"detail": "The dispute attestation has expired."
}Implementations SHOULD:
- Rate limit dispute filing by issuer
- Require proof of relationship to disputed content
- Validate that
target_refexists before accepting disputes - Consider reputation scoring for frequent filers
Implementations MUST:
- Not expose full dispute content to unauthorized parties
- Redact PII from audit logs
- Consider jurisdiction requirements for dispute data
Implementations SHOULD:
- Minimize
descriptionfield content in logs - Hash
target_refin analytics - Provide data retention policies
Implementations SHOULD:
- Sign dispute attestations with Ed25519
- Verify signatures before state transitions
- Maintain immutable audit trail of state changes
- Use content hashes for supporting documents
Implementations SHOULD:
- Verify issuer has standing to file dispute
- Restrict state transitions to authorized parties
- Log all state changes with actor identity
Dispute IDs use ULID (Universally Unique Lexicographically Sortable Identifier) format:
- 26 characters total
- Crockford Base32 encoding (excludes I, L, O, U)
- First 10 characters: timestamp (milliseconds since Unix epoch)
- Last 16 characters: randomness
Valid characters: 0123456789ABCDEFGHJKMNPQRSTVWXYZ
Canonical form requires UPPERCASE; non-canonical input is rejected by the verifier:
- Generators MUST produce uppercase
- Validators MAY normalize lowercase but SHOULD warn
- Comparisons MUST use uppercase
01ARZ3NDEKTSV4RRFFQ69G5FAV
└─────────┴────────────────┘
timestamp randomness
The @peac/audit package defines these dispute-related event types:
| Event Type | When Emitted |
|---|---|
dispute_filed |
New dispute attestation created |
dispute_acknowledged |
Dispute state -> acknowledged |
dispute_resolved |
Dispute state -> resolved |
dispute_rejected |
Dispute state -> rejected |
dispute_appealed |
Dispute state -> appealed |
dispute_final |
Dispute state -> final |
Disputes can be collected into a case bundle for resolution:
interface CaseBundle {
version: 'peac.bundle/0.9';
dispute_ref: string; // ULID of the dispute
generated_at: string; // When bundle was created
generated_by: string; // Who generated it
entries: AuditEntry[]; // Related audit entries
trace_ids: string[]; // W3C trace IDs involved
summary: CaseBundleSummary;
}Audit entries use W3C Trace Context for correlation:
interface TraceContext {
trace_id: string; // 32 hex characters
span_id: string; // 16 hex characters
parent_span_id?: string;
}- v0.9.27: Initial dispute attestation specification