Normativity: Normative
Version: 1.0.0-draft Date: 2026-04-18 Status: [IMPLEMENTED] Phase 1 ticket flow (PENDING → IN_PROGRESS → COMPLETED/DISPUTED/CANCELLED) | [IMPLEMENTED] Ticket types SMART_ORDER, MILESTONE, DISPUTE, GOVERNANCE, VERIFICATION, ROYALTY | [IMPLEMENTED] OfferType SERVICE, PRODUCT, ITEM, COOP (ADR-022, ADR-066, ADR-113) | [IMPLEMENTED] Extended statuses PAID (ADR-121), ARBITRATION_LOST/WON (ADR-125/126) | [RESERVED] INTEREST_SENT (ADR-129, PROB-001)
The ticket is the core of Colabonate. Every interaction between parties is represented as a ticket — it is simultaneously a contract, a payment instruction, and a communication channel.
(PDC: see ADR-021) – Added OfferType SERVICE vs PRODUCT distinction
The schema has a single ticket type with these fields:
model Ticket {
id String @id @default(cuid())
offerId String
buyerPubkey String // = Partner
sellerPubkey String // = Initiator
status String @default("PENDING") // Volatile: TicketStatus values, zod-validated
lightningInvoice String?
paymentHash String?
amountSats Int?
disputeReason String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}Active API endpoints:
POST /api/tickets Create ticket
GET /api/tickets?pubkey=... Get tickets for a user
GET /api/tickets/:id Ticket details
PATCH /api/tickets/:id Update status
GET /api/tickets/:id/payment-status Payment polling (LNBits)
(PDC: see ADR-021) – Added OfferType SERVICE vs PRODUCT distinction
Additional statuses in code: The
TicketStatustype validated by Zod also includesINTEREST_SENT(reserved/unused — cooperation-interest flow per PROB-001, ADR-129),PAID(cooperation escrow, ADR-121), andARBITRATION_LOST/ARBITRATION_WON(dispute resolution, ADR-125/126). These are not shown in the Phase 1 flow above.
PENDING
├─► (accept) ─► IN_PROGRESS ─► COMPLETED
├─► (reject) ─► CANCELLED
└─► (either) ─► DISPUTED ─► COMPLETED | CANCELLED
ACCEPTEDis in the schema but not yet active in routing. The transition currently goes directly: PENDING → IN_PROGRESS viaaction: accept.
The Phase 1 schema is extended in Phase 2 with legal binding fields, ticket type, and offer type:
model Ticket {
id String @id @default(cuid())
offerId String
buyerPubkey String
sellerPubkey String
status String @default("PENDING") // Volatile: TicketStatus values, zod-validated
offerType OfferType @default(SERVICE) // NEW: SERVICE | PRODUCT
ticketType TicketType @default(SMART_ORDER)
daoId String? // DAO binding (see legal-binding-layer.md)
codexHash String? // SHA-256 of Codex at binding time
codexVersion String? // Human-readable Codex version (e.g. "1.2")
escrowStatus String @default("NONE") // Volatile: EscrowStatus values, zod-validated
// Escrow phase invoices
phase1Invoice String?
phase2Invoice String?
phase3Invoice String?
deliveryConfirmedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
enum OfferType {
SERVICE // 1x per buyer, seller must accept
PRODUCT // unlimited quantity, auto-accepted
}
enum TicketType {
SMART_ORDER
MILESTONE
DISPUTE
GOVERNANCE
VERIFICATION
ROYALTY
}
// NOTE: ADR-139 removed Prisma enums for status fields in favor of zod validation at the app boundary.
// EscrowStatus values (zod-validated, see escrow-protocol.md):
// NONE, INITIATED, PHASE_1_PENDING, RESERVED, PHASE_2_PENDING, DELIVERY_STARTED, PHASE_3_PENDING, DELIVERY_CONFIRMED, RELEASED, CANCELLED, DISPUTED, FUNDED, RELEASE_PHASE_1, RELEASE_PHASE_2, RELEASE_PHASE_3, RELEASED_FULL, REFUNDED
⚠️ EscrowStatus note: The Prisma schema uses string validation (EscrowStatusSchema). The active set (INITIATED, PHASE_1_PENDING, RESERVED, etc.) is defined in escrow-protocol.md and used byapps/server/routes/escrow.ts(ADR-124). The legacy set (FUNDED, RELEASE_PHASE_1, etc.) from an earlier spec draft remains valid in the Zod schema but is unused.(PDC: see ADR-124)
OfferType Behavior:
| OfferType | Status after creation | Seller approval required | UI Action |
|---|---|---|---|
| SERVICE | PENDING | Yes | "Request" |
| PRODUCT | IN_PROGRESS | No (auto-accepted) | "Buy Now" |
Status Flow (Phase 2):
SERVICE Flow:
PENDING ──(accept)──► IN_PROGRESS ──► PAID ──► COMPLETED
(reject)──► CANCELLED
PRODUCT Flow:
IN_PROGRESS ──► PAID ──► COMPLETED (no PENDING state)
Both Flows:
Any state ──► DISPUTED ──► COMPLETED | CANCELLED
When daoId + codexHash are set, the ticket is bound to a DAO Codex.
See legal-binding-layer.md for full specification.
Buy/sell with Lightning escrow phases.
Planned escrow structure:
- 25% on ticket acceptance (reservation)
- 50% on delivery start / milestone
- 25% on completion and review
Required schema changes:
ticketType TicketType @default(SMART_ORDER)
escrowStatus String @default("NONE") // Volatile: EscrowStatus values, zod-validated
phase1Invoice String?
phase2Invoice String?
phase3Invoice String?
deliveryConfirmedAt DateTime?Project progress tracking for cooperations. Each milestone has its own acceptance criteria and a separate Lightning invoice for partial payment.
Requires new Prisma model:
model Milestone {
id String @id @default(cuid())
ticketId String
ticket Ticket @relation(...)
title String
description String?
dueDate DateTime?
status MilestoneStatus @default(PENDING)
invoice String?
amountSats Int?
completedAt DateTime?
}Three-level conflict resolution — see workflows/dispute-protocol.md.
Trigger:
- Manual:
PATCH /api/tickets/:id { status: "DISPUTED", disputeReason: "..." } - Automatic: timeout without completion [PHASE 2]
DAO votes via Nostr Events. Initial voting weight: 1 pubkey = 1 vote.
Identity and skill verification. Non-transferable (Soulbound). Technically: a Nostr Event co-signed by a verified pubkey.
No NFT, no EVM — purely Bitcoin/Nostr-based.
Automatic royalty distribution when community protocols and workflows are used. Based on Lightning Keysend or a similar mechanism.
Source: Whitepaper v6, Section 3.3
Dedicated ticket type for capturing structured feedback and building reputation scores. Distinct from the general review system (Kind 30024) — a Rating Ticket is a formal, protocol-level rating artifact that is:
- Linked to a completed Smart Order or Milestone Ticket
- Structured (star rating + dimensions + free text)
- Published as a Nostr event (Kind 30024)
- Triggers COL-Points award upon mutual submission
Relationship to reputation-protocol.md: The Rating Ticket is the protocol-level trigger for a Kind 30024 event. It is not a new Nostr event kind — it is the formal process that creates one.
Schema additions:
ticketType TicketType // RATING
parentTicketId String // reference to completed ticket being rated
ratingFor String // pubkey of the person being rated
stars Int // 1-5
dimensions Json? // optional structured rating dimensions
reviewText String? // free text (max 2048 chars)
nostrEventId String? // Kind 30024 event published on completion
Status flow:
PENDING → SUBMITTED (rating text entered)
→ PUBLISHED (Kind 30024 event published on Nostr)
→ MUTUAL_COMPLETE (both parties have submitted)
Source: Whitepaper v6, Section 3.3
Handles product returns, service complaints, and refund processes. Initiated by the buyer after a ticket is COMPLETED (within a return window defined in the original offer).
Properties:
- Buyer-initiated only
- Linked to a COMPLETED Smart Order Ticket
- Opens a structured return/refund process independent of the main dispute protocol
- Lower friction than a Dispute Ticket — designed for cases where return is agreed upon, not contested
- If return is contested → escalates to Dispute Ticket (Level 1)
Return window: Defined by seller in the offer metadata (["return_window", "<days>"] tag on Kind 30017). Default: 0 days (no returns) unless explicitly set.
Schema additions:
ticketType TicketType // RETURN
parentTicketId String // reference to COMPLETED ticket
returnReason String // reason slug: defective | not_as_described | changed_mind | other
returnDescription String? // free text explanation
evidenceRefs String[] // references to evidence (hashes of photos, etc.)
refundAmountSats Int? // proposed refund amount (may be partial)
returnStatus ReturnStatus // REQUESTED | SELLER_ACCEPTED | IN_TRANSIT | COMPLETED | DISPUTED
Status flow:
REQUESTED
├── Seller accepts → SELLER_ACCEPTED
│ └── Buyer confirms return sent → IN_TRANSIT
│ └── Seller confirms receipt → COMPLETED (refund released from escrow)
└── Seller rejects → DISPUTED (escalates to Dispute Ticket Level 1)
Note on escrow: For a Return Ticket to trigger an automatic refund, the original ticket must have used escrow (Phase 2+). Phase 1 returns are handled off-protocol.
Note on Reputation Tickets (Section 3.3): The Whitepaper lists "Reputation Tickets" separately. In the protocol architecture, reputation is managed through Rating Tickets (Type 7) and the Verification Ticket (Type 5, existing). A separate Reputation Ticket type may be introduced in Phase 5 for documenting community contributions and SBT management. Flagged as [CONCEPT] pending further specification.
Smart Order Ticket
└─► can escalate to ────► Dispute Ticket
Milestone Ticket
└─► extends ────────────► Smart Order Ticket (parent)
└─► can escalate to ────► Dispute Ticket
Governance Ticket
└─► can affect ─────────► Dispute Ticket (arbitrator election)
Verification Ticket
└─► required for ───────► Mediator role (Phase 4)
| Area | Ticket Type | Phase |
|---|---|---|
| Buy / Sell | Smart Order Ticket | Phase 2 (base: Phase 1) |
| Project / Cooperation | Milestone Ticket | Phase 2 |
| Conflict | Dispute Ticket | Phase 4 |
| Voting | Governance Ticket | Phase 4+ |
| Identity / Skills | Verification Ticket | Phase 3 |
| Protocol usage | Royalty Ticket | Phase 5 |
| Structured feedback | Rating Ticket | Phase 3 |
| Returns / Refunds | Return Ticket | Phase 4 |
Part of the Colabonate Protocol Specification | docs/protocols/