@@ -21,6 +21,18 @@ pub const REASON_MAX_LEN: u32 = 128;
2121/// finalize_claim may be called to settle the outcome.
2222pub const VOTE_WINDOW_LEDGERS : u32 = 120_960 ;
2323
24+ /// Maximum number of appeals allowed per claim.
25+ /// Prevents infinite ping-pong of voting rounds.
26+ pub const MAX_APPEALS_PER_CLAIM : u32 = 1 ;
27+
28+ /// Appeal window in ledgers (~3 days at 5 s/ledger ≈ 51_840 ledgers).
29+ /// Claimant must open appeal within this window after rejection.
30+ pub const APPEAL_WINDOW_LEDGERS : u32 = 51_840 ;
31+
32+ /// Appeal voting window in ledgers (~5 days at 5 s/ledger ≈ 86_400 ledgers).
33+ /// Shorter than initial vote to expedite resolution.
34+ pub const APPEAL_VOTE_WINDOW_LEDGERS : u32 = 86_400 ;
35+
2436// ── policy_id assignment ─────────────────────────────────────────────────────
2537//
2638// policy_id is a u32 scoped per holder: the contract increments a per-holder
@@ -48,6 +60,11 @@ pub enum ClaimError {
4860 ClaimAlreadyFinalized = 9 ,
4961 VoterHasNoPolicies = 10 ,
5062 AlreadyVoted = 11 ,
63+ AppealNotAllowed = 12 ,
64+ AppealWindowExpired = 13 ,
65+ MaxAppealsReached = 14 ,
66+ NotClaimant = 15 ,
67+ AppealNotInProgress = 16 ,
5168}
5269
5370// ── Enums ────────────────────────────────────────────────────────────────────
@@ -72,34 +89,52 @@ pub enum RegionTier {
7289 High , // urban / high-risk zone
7390}
7491
75- /// Claim lifecycle state machine.
92+ /// Claim lifecycle state machine with appeals support .
7693///
7794/// ```text
7895/// [filed] → Processing
7996/// │
8097/// ┌──────┴──────┐
8198/// ▼ ▼
82- /// Approved Rejected
99+ /// Approved Rejected ──┐
100+ /// │ │ (appeal window)
101+ /// ▼ │
102+ /// AppealOpen ◄─┘
103+ /// │
104+ /// ┌──────┴──────┐
105+ /// ▼ ▼
106+ /// Approved RejectedFinal
83107/// ```
84108///
85109/// Transitions:
86- /// Processing → Approved : majority Approve votes reached
87- /// Processing → Rejected : majority Reject votes reached OR policy deactivated
110+ /// Processing → Approved : majority Approve votes reached
111+ /// Processing → Rejected : majority Reject votes reached
112+ /// Rejected → AppealOpen : claimant opens appeal within window
113+ /// Rejected → RejectedFinal : appeal window expires without appeal
114+ /// AppealOpen → Approved : majority Approve votes on appeal
115+ /// AppealOpen → RejectedFinal : majority Reject votes on appeal
88116///
89- /// Terminal states (Approved / Rejected ) are immutable; no re-open path exists
90- /// on-chain. Off-chain dispute resolution must open a new claim .
117+ /// Terminal states (Approved / RejectedFinal ) are immutable.
118+ /// Appeals are capped at MAX_APPEALS_PER_CLAIM to prevent infinite loops .
91119#[ contracttype]
92120#[ derive( Clone , PartialEq , Debug ) ]
93121pub enum ClaimStatus {
94122 Processing ,
95123 Approved ,
96124 Rejected ,
125+ AppealOpen ,
126+ RejectedFinal ,
97127}
98128
99129impl ClaimStatus {
100- /// Returns true only for the two terminal states.
130+ /// Returns true only for the terminal states.
101131 pub fn is_terminal ( & self ) -> bool {
102- matches ! ( self , ClaimStatus :: Approved | ClaimStatus :: Rejected )
132+ matches ! ( self , ClaimStatus :: Approved | ClaimStatus :: RejectedFinal )
133+ }
134+
135+ /// Returns true if the claim is in an active voting phase.
136+ pub fn is_voting_active ( & self ) -> bool {
137+ matches ! ( self , ClaimStatus :: Processing | ClaimStatus :: AppealOpen )
103138 }
104139}
105140
@@ -155,17 +190,21 @@ pub struct Policy {
155190
156191/// On-chain claim record.
157192///
158- /// | Field | Authoritative | Notes |
159- /// |---------------|---------------|-------|
160- /// | claim_id | on-chain | global monotonic u64 from ClaimCounter |
161- /// | policy_id | on-chain | references Policy(holder, policy_id) |
162- /// | claimant | on-chain | must equal policy.holder |
163- /// | amount | on-chain | stroops; 0 < amount ≤ policy.coverage |
164- /// | details | on-chain | ≤ DETAILS_MAX_LEN bytes |
165- /// | image_urls | on-chain | ≤ IMAGE_URLS_MAX items, each ≤ IMAGE_URL_MAX_LEN |
166- /// | status | on-chain | ClaimStatus state machine |
167- /// | approve_votes | on-chain | running tally |
168- /// | reject_votes | on-chain | running tally |
193+ /// | Field | Authoritative | Notes |
194+ /// |------------------------|---------------|-------|
195+ /// | claim_id | on-chain | global monotonic u64 from ClaimCounter |
196+ /// | policy_id | on-chain | references Policy(holder, policy_id) |
197+ /// | claimant | on-chain | must equal policy.holder |
198+ /// | amount | on-chain | stroops; 0 < amount ≤ policy.coverage |
199+ /// | details | on-chain | ≤ DETAILS_MAX_LEN bytes |
200+ /// | image_urls | on-chain | ≤ IMAGE_URLS_MAX items, each ≤ IMAGE_URL_MAX_LEN |
201+ /// | status | on-chain | ClaimStatus state machine |
202+ /// | approve_votes | on-chain | running tally |
203+ /// | reject_votes | on-chain | running tally |
204+ /// | filed_at_ledger | on-chain | ledger when claim was filed |
205+ /// | rejected_at_ledger | on-chain | ledger when claim was rejected (for appeal window) |
206+ /// | appeal_count | on-chain | number of appeals opened (capped at MAX_APPEALS_PER_CLAIM) |
207+ /// | appeal_opened_at_ledger| on-chain | ledger when current appeal was opened |
169208#[ contracttype]
170209#[ derive( Clone ) ]
171210pub struct Claim {
@@ -181,6 +220,14 @@ pub struct Claim {
181220 pub status : ClaimStatus ,
182221 pub approve_votes : u32 ,
183222 pub reject_votes : u32 ,
223+ /// Ledger sequence when claim was filed.
224+ pub filed_at_ledger : u32 ,
225+ /// Ledger sequence when claim was rejected (used for appeal window).
226+ pub rejected_at_ledger : Option < u32 > ,
227+ /// Number of appeals opened for this claim.
228+ pub appeal_count : u32 ,
229+ /// Ledger sequence when current appeal was opened.
230+ pub appeal_opened_at_ledger : Option < u32 > ,
184231}
185232
186233/// Premium quote line item for UX display.
0 commit comments