package alert import ( "context" "errors" "fmt" "time" "github.com/jackc/pgx/v5" ) func Unacknowledge(current Snapshot, actor string, at time.Time) (TransitionResult, error) { current = current.normalized() if actor == "" || len(actor) > 160 || at.IsZero() { return TransitionResult{}, ErrInvalidObservation } if current.State != StateAcknowledged { return TransitionResult{}, ErrStateConflict } result := current result.State = StateFiring result.RetainedState = StateFiring result.AcknowledgedBy = "" result.AcknowledgedAt = nil result.Reason = "unacknowledged" return finish(current, result, StateFiring, "unacknowledge"), nil } func (r StateRepository) AcknowledgeRevision(ctx context.Context, instanceID, actor, evaluationKey string, at time.Time, expectedRevision int64) (Instance, Occurrence, bool, error) { return r.applyOperation(ctx, instanceID, actor, evaluationKey, at, expectedRevision, true) } func (r StateRepository) Unacknowledge(ctx context.Context, instanceID, actor, evaluationKey string, at time.Time, expectedRevision int64) (Instance, Occurrence, bool, error) { return r.applyOperation(ctx, instanceID, actor, evaluationKey, at, expectedRevision, false) } func (r StateRepository) applyOperation(ctx context.Context, instanceID, actor, evaluationKey string, at time.Time, expectedRevision int64, acknowledge bool) (Instance, Occurrence, bool, error) { if r.Pool == nil { return Instance{}, Occurrence{}, false, ErrUnavailable } if instanceID == "" || actor == "" || len(actor) > 160 || evaluationKey == "" || len(evaluationKey) > 160 || at.IsZero() || expectedRevision < 1 { return Instance{}, Occurrence{}, false, ErrInvalidObservation } tx, err := r.Pool.BeginTx(ctx, pgx.TxOptions{}) if err != nil { return Instance{}, Occurrence{}, false, fmt.Errorf("begin alert operation: %w", err) } defer func() { _ = tx.Rollback(ctx) }() var current Instance if err := scanInstance(tx.QueryRow(ctx, `SELECT id,rule_id,rule_version_id,fingerprint,COALESCE(entity_id::text,''),current_state,retained_state,active_since,recovery_since,cooldown_until,last_evaluated_at,last_known_at,last_value,reason,source_health,COALESCE(acknowledged_by,''),acknowledged_at,revision,created_at,updated_at FROM alert_instances WHERE id=$1 FOR UPDATE`, instanceID), ¤t); errors.Is(err, ErrInstanceNotFound) { return Instance{}, Occurrence{}, false, ErrInstanceNotFound } else if err != nil { return Instance{}, Occurrence{}, false, err } if occurrence, err := scanOccurrence(tx.QueryRow(ctx, `SELECT id,instance_id,evaluation_key,event_type,from_state,to_state,observed_at,value,reason,source_health,created_at FROM alert_occurrences WHERE instance_id=$1 AND evaluation_key=$2`, instanceID, evaluationKey)); err == nil { if err := tx.Commit(ctx); err != nil { return Instance{}, Occurrence{}, false, fmt.Errorf("commit idempotent alert operation: %w", err) } return current, occurrence, true, nil } else if !errors.Is(err, pgx.ErrNoRows) { return Instance{}, Occurrence{}, false, err } if current.Revision != expectedRevision { return Instance{}, Occurrence{}, false, ErrRevisionConflict } var transition TransitionResult if acknowledge { transition, err = Acknowledge(snapshotFromInstance(current), actor, at.UTC()) } else { transition, err = Unacknowledge(snapshotFromInstance(current), actor, at.UTC()) } if err != nil { return Instance{}, Occurrence{}, false, err } valueJSON, err := boundedJSON(current.LastValue, 128<<10) if err != nil { return Instance{}, Occurrence{}, false, err } healthJSON, err := boundedJSON(nonNilMap(current.SourceHealth), 64<<10) if err != nil { return Instance{}, Occurrence{}, false, err } acknowledgedBy := nullableText(transition.Snapshot.AcknowledgedBy) acknowledgedAt := transition.Snapshot.AcknowledgedAt if _, err := tx.Exec(ctx, `UPDATE alert_instances SET current_state=$1,retained_state=$2,reason=$3,acknowledged_by=$4,acknowledged_at=$5,revision=revision+1,updated_at=now() WHERE id=$6 AND revision=$7`, transition.Snapshot.State, transition.Snapshot.RetainedState, transition.Snapshot.Reason, acknowledgedBy, acknowledgedAt, instanceID, expectedRevision); err != nil { return Instance{}, Occurrence{}, false, mapStateError(fmt.Errorf("update alert operation: %w", err)) } occurrence, err := insertOccurrence(ctx, tx, instanceID, evaluationKey, transition, Observation{ObservedAt: at.UTC(), Reason: transition.Snapshot.Reason, Value: current.LastValue}, valueJSON, healthJSON) if err != nil { return Instance{}, Occurrence{}, false, err } if err := scanInstance(tx.QueryRow(ctx, `SELECT id,rule_id,rule_version_id,fingerprint,COALESCE(entity_id::text,''),current_state,retained_state,active_since,recovery_since,cooldown_until,last_evaluated_at,last_known_at,last_value,reason,source_health,COALESCE(acknowledged_by,''),acknowledged_at,revision,created_at,updated_at FROM alert_instances WHERE id=$1`, instanceID), ¤t); err != nil { return Instance{}, Occurrence{}, false, err } if err := tx.Commit(ctx); err != nil { return Instance{}, Occurrence{}, false, fmt.Errorf("commit alert operation: %w", err) } return current, occurrence, false, nil }