@@ -7,6 +7,25 @@ pub const IMAGE_URLS_MAX: u32 = 5;
77pub const REASON_MAX_LEN : u32 = 128 ;
88pub const SAFETY_SCORE_MAX : u32 = 100 ;
99
10+ // ── Rejection side-effect thresholds ─────────────────────────────────────────
11+ //
12+ // GOVERNANCE NOTE: This constant is the only on-chain parameter controlling
13+ // automatic policy deactivation. Changing it requires a contract upgrade and
14+ // cannot be altered by the admin at runtime — removing an avenue for
15+ // admin-only extraction via strike-count manipulation.
16+ //
17+ // LEGAL NOTE: Product/legal must sign off on the strike threshold before
18+ // mainnet deployment. Three rejections is a conservative starting point.
19+ // The threshold intentionally errs toward coverage preservation; false
20+ // positives (legitimate holders de-activated) are harder to recover from
21+ // than false negatives (fraudulent holders retained until human review).
22+ //
23+ // APPEAL INTERACTION: If an appeal window is added later, auto-deactivation
24+ // should be deferred until the appeal deadline passes. Implement by adding a
25+ // `deactivation_pending_until_ledger: u32` field to Policy and skipping the
26+ // `is_active = false` write until that ledger is reached.
27+ pub const STRIKE_DEACTIVATION_THRESHOLD : u32 = 3 ;
28+
1029// ── Ledger window constants (re-exported from ledger.rs for ABI visibility) ───
1130//
1231// These are the canonical values used by on-chain checks. The frontend and
@@ -124,6 +143,15 @@ pub enum VoteOption {
124143}
125144
126145/// Reason for policy termination.
146+ ///
147+ /// GOVERNANCE NOTE: `ExcessiveRejections` is set by the claims engine
148+ /// automatically when `strike_count` reaches `STRIKE_DEACTIVATION_THRESHOLD`.
149+ /// All other variants require an explicit holder or admin action.
150+ ///
151+ /// CENTRALIZATION RISK: `AdminOverride` allows the admin to terminate any
152+ /// policy for any reason at any time. This is a privileged operation that
153+ /// bypasses normal holder protections. Consider a time-lock or multi-sig
154+ /// requirement before using this variant in production.
127155#[ contracttype]
128156#[ derive( Clone , PartialEq , Eq , Debug ) ]
129157pub enum TerminationReason {
@@ -134,9 +162,15 @@ pub enum TerminationReason {
134162 FraudOrMisrepresentation ,
135163 RegulatoryAction ,
136164 AdminOverride ,
137- /// Policy deactivated automatically by `on_reject` when `strike_count` reached
138- /// `STRIKE_DEACTIVATION_THRESHOLD`. Not set by admin.
139- /// **XDR append-safe:** appended at end of enum; existing serialised values unchanged.
165+ /// Automatically set when `Policy.strike_count` reaches
166+ /// `STRIKE_DEACTIVATION_THRESHOLD` consecutive rejections.
167+ /// No admin intervention is required or possible to prevent this;
168+ /// the transition is deterministic and trustless.
169+ ///
170+ /// APPEAL NOTE: If an appeal window is introduced, deactivation should be
171+ /// deferred until the appeal window closes. The `PolicyDeactivated` event
172+ /// (emitted in `claim.rs`) is the authoritative signal for indexers; it
173+ /// will carry a `reason_code = 1` identifying this variant.
140174 ExcessiveRejections ,
141175}
142176
@@ -199,11 +233,21 @@ pub struct Policy {
199233 pub terminated_by_admin : bool ,
200234 /// Running count of rejected claims against this policy.
201235 ///
202- /// Incremented by `claim::on_reject`. When `strike_count >= STRIKE_DEACTIVATION_THRESHOLD`
203- /// the policy is automatically deactivated (`is_active = false`) and a `PolicyDeactivated`
204- /// event is emitted. A successful appeal decrements this counter.
236+ /// Incremented by `claim::on_reject` every time a claim on this policy
237+ /// reaches `ClaimStatus::Rejected` (whether via majority vote or deadline
238+ /// finalization). Never decremented; exists purely for accumulation.
239+ ///
240+ /// When `strike_count >= STRIKE_DEACTIVATION_THRESHOLD`, the policy is
241+ /// automatically deactivated (`is_active = false`) and the
242+ /// `PolicyDeactivated` event is emitted. No admin action is required.
243+ ///
244+ /// RENEWAL GATE: Any future `renew_policy` implementation MUST check
245+ /// `strike_count` before allowing renewal. A policy with strikes at or
246+ /// near the threshold should be blocked or require admin review.
205247 ///
206- /// **Renewal gate:** any future `renew_policy` implementation MUST gate on this field.
248+ /// DATA VISIBILITY: This field is stored on-chain and permanently
249+ /// readable via `get_policy`. It carries only a count — no allegation
250+ /// narratives, no claimant-identifying data.
207251 pub strike_count : u32 ,
208252}
209253
0 commit comments