feat: dismiss reviewed settlement failures
CI/CD Stage / stage (push) Failing after 37s

This commit is contained in:
2026-10-07 00:32:20 +03:30
parent 88e227f80c
commit d33ec51201
13 changed files with 385 additions and 57 deletions
@@ -147,8 +147,8 @@ func (r *SettlementRepository) RetryByOperator(ctx context.Context, id, actorID,
if err := tx.QueryRow(ctx, `SELECT status FROM kuknos_settlements WHERE id=$1 FOR UPDATE`, id).Scan(&previous); err != nil {
return ledger.Settlement{}, fmt.Errorf("lock settlement for operator retry: %w", err)
}
if previous == ledger.SettlementConfirmed {
return ledger.Settlement{}, fmt.Errorf("confirmed settlement cannot be retried")
if previous == ledger.SettlementConfirmed || previous == ledger.SettlementIgnored {
return ledger.Settlement{}, fmt.Errorf("confirmed or ignored settlement cannot be retried")
}
if _, err := tx.Exec(ctx, `UPDATE kuknos_settlements SET status='RETRYABLE', available_at=$2, locked_at=NULL, locked_by='', last_error='', manual_review_at=NULL, updated_at=clock_timestamp() WHERE id=$1`, id, now); err != nil {
return ledger.Settlement{}, fmt.Errorf("retry settlement: %w", err)
@@ -162,6 +162,35 @@ func (r *SettlementRepository) RetryByOperator(ctx context.Context, id, actorID,
return r.Get(ctx, id, "")
}
func (r *SettlementRepository) IgnoreByOperator(ctx context.Context, id, actorID, reason string) (ledger.Settlement, error) {
tx, err := r.database.Begin(ctx)
if err != nil {
return ledger.Settlement{}, fmt.Errorf("begin operator dismissal: %w", err)
}
defer tx.Rollback(ctx)
var previous, lockedBy string
if err := tx.QueryRow(ctx, `SELECT status, locked_by FROM kuknos_settlements WHERE id=$1 FOR UPDATE`, id).Scan(&previous, &lockedBy); err != nil {
return ledger.Settlement{}, fmt.Errorf("lock settlement for dismissal: %w", err)
}
if lockedBy != "" || (previous != string(ledger.SettlementManualReview) && previous != string(ledger.SettlementRetryable)) {
return ledger.Settlement{}, fmt.Errorf("only unlocked settlements requiring review or retry can be ignored")
}
// Preserve the error and signed payload as evidence; this is not a payment confirmation.
if _, err := tx.Exec(ctx, `UPDATE kuknos_settlements SET status='IGNORED', updated_at=clock_timestamp() WHERE id=$1`, id); err != nil {
return ledger.Settlement{}, fmt.Errorf("ignore settlement: %w", err)
}
if _, err := tx.Exec(ctx, `INSERT INTO kuknos_settlement_operator_audits (settlement_id, actor_id, action, reason, previous_status, resulting_status) VALUES ($1,$2,'IGNORE',$3,$4,'IGNORED')`, id, actorID, reason, previous); err != nil {
return ledger.Settlement{}, fmt.Errorf("audit operator dismissal: %w", err)
}
if _, err := tx.Exec(ctx, `UPDATE kuknos_reconciliation_issues SET status='RESOLVED', resolved_at=clock_timestamp() WHERE settlement_id=$1 AND status='OPEN'`, id); err != nil {
return ledger.Settlement{}, fmt.Errorf("resolve dismissed settlement issues: %w", err)
}
if err := tx.Commit(ctx); err != nil {
return ledger.Settlement{}, fmt.Errorf("commit operator dismissal: %w", err)
}
return r.Get(ctx, id, "")
}
// ReconcileConfirmationEvidence durably records confirmed Kuknos settlements
// whose latest GL lifecycle evidence is missing or has a different hash.
func (r *SettlementRepository) ReconcileConfirmationEvidence(ctx context.Context, grace time.Duration) (ReconciliationStats, error) {