src/lightspeed@ops@slo_autopilot.erl

-module(lightspeed@ops@slo_autopilot).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/lightspeed/ops/slo_autopilot.gleam").
-export([sample/5, thresholds/4, default_thresholds/0, new/1, active_controls/1, rollback/6, apply/6, recommended_controls/2, classify/2, evaluate/4, runtime_thresholds/1, audit_entries/1, reversible/1, status_label/1, control_label/1, decision_label/1, sample_label/1, thresholds_label/1, audit_label/1, audit_metric/1, valid/1, signature/1]).
-export_type([burn_rate_sample/0, thresholds/0, status/0, control/0, decision/0, audit_entry/0, runtime/0]).

-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.

?MODULEDOC(" SLO burn-rate and mitigation-control contracts for M30.\n").

-type burn_rate_sample() :: {burn_rate_sample,
        binary(),
        binary(),
        integer(),
        integer(),
        binary()}.

-type thresholds() :: {thresholds, integer(), integer(), integer(), integer()}.

-type status() :: healthy | warning | critical.

-type control() :: {shed_events, integer()} |
    pause_background_jobs |
    {isolate_hotspot_tenant, binary()} |
    enter_read_only_mode.

-type decision() :: applied | rolled_back | {noop, binary()}.

-type audit_entry() :: {audit_entry,
        binary(),
        control(),
        decision(),
        binary(),
        integer(),
        status()}.

-opaque runtime() :: {runtime,
        thresholds(),
        list(control()),
        list(audit_entry())}.

-file("src/lightspeed/ops/slo_autopilot.gleam", 72).
?DOC(" Build one burn-rate sample.\n").
-spec sample(binary(), binary(), integer(), integer(), binary()) -> burn_rate_sample().
sample(
    Service,
    Slo_name,
    Short_window_milli,
    Long_window_milli,
    Hotspot_tenant_id
) ->
    {burn_rate_sample,
        Service,
        Slo_name,
        Short_window_milli,
        Long_window_milli,
        Hotspot_tenant_id}.

-file("src/lightspeed/ops/slo_autopilot.gleam", 89).
?DOC(" Build one threshold profile.\n").
-spec thresholds(integer(), integer(), integer(), integer()) -> thresholds().
thresholds(
    Warn_short_milli,
    Warn_long_milli,
    Critical_short_milli,
    Critical_long_milli
) ->
    {thresholds,
        Warn_short_milli,
        Warn_long_milli,
        Critical_short_milli,
        Critical_long_milli}.

-file("src/lightspeed/ops/slo_autopilot.gleam", 104).
?DOC(" Default M30 threshold profile.\n").
-spec default_thresholds() -> thresholds().
default_thresholds() ->
    {thresholds, 1000, 700, 2000, 1500}.

-file("src/lightspeed/ops/slo_autopilot.gleam", 114).
?DOC(" Build one autopilot runtime.\n").
-spec new(thresholds()) -> runtime().
new(Thresholds) ->
    {runtime, Thresholds, [], []}.

-file("src/lightspeed/ops/slo_autopilot.gleam", 249).
?DOC(" Active controls in stable order.\n").
-spec active_controls(runtime()) -> list(control()).
active_controls(Runtime) ->
    lists:reverse(erlang:element(3, Runtime)).

-file("src/lightspeed/ops/slo_autopilot.gleam", 455).
-spec record_audit(runtime(), audit_entry()) -> runtime().
record_audit(Runtime, Entry) ->
    {runtime,
        erlang:element(2, Runtime),
        erlang:element(3, Runtime),
        [Entry | erlang:element(4, Runtime)]}.

-file("src/lightspeed/ops/slo_autopilot.gleam", 484).
-spec remove_control(list(control()), control()) -> list(control()).
remove_control(Controls, Target) ->
    case Controls of
        [] ->
            [];

        [Control | Rest] ->
            case Control =:= Target of
                true ->
                    Rest;

                false ->
                    [Control | remove_control(Rest, Target)]
            end
    end.

-file("src/lightspeed/ops/slo_autopilot.gleam", 473).
-spec has_control(list(control()), control()) -> boolean().
has_control(Controls, Target) ->
    case Controls of
        [] ->
            false;

        [Control | Rest] ->
            case Control =:= Target of
                true ->
                    true;

                false ->
                    has_control(Rest, Target)
            end
    end.

-file("src/lightspeed/ops/slo_autopilot.gleam", 203).
?DOC(" Roll back one control with an audit trail entry.\n").
-spec rollback(runtime(), control(), binary(), binary(), integer(), status()) -> runtime().
rollback(Runtime, Control, Actor_id, Reason, At_ms, Status) ->
    case has_control(erlang:element(3, Runtime), Control) of
        true ->
            _pipe = {runtime,
                erlang:element(2, Runtime),
                remove_control(erlang:element(3, Runtime), Control),
                erlang:element(4, Runtime)},
            record_audit(
                _pipe,
                {audit_entry,
                    Actor_id,
                    Control,
                    rolled_back,
                    Reason,
                    At_ms,
                    Status}
            );

        false ->
            record_audit(
                Runtime,
                {audit_entry,
                    Actor_id,
                    Control,
                    {noop, <<"not_active"/utf8>>},
                    Reason,
                    At_ms,
                    Status}
            )
    end.

-file("src/lightspeed/ops/slo_autopilot.gleam", 422).
-spec rollback_loop(
    list(control()),
    runtime(),
    list(control()),
    binary(),
    integer(),
    status()
) -> runtime().
rollback_loop(Current, Runtime, Desired, Actor_id, Now_ms, Status) ->
    case Current of
        [] ->
            Runtime;

        [Control | Rest] ->
            case has_control(Desired, Control) of
                true ->
                    rollback_loop(
                        Rest,
                        Runtime,
                        Desired,
                        Actor_id,
                        Now_ms,
                        Status
                    );

                false ->
                    rollback_loop(
                        Rest,
                        rollback(
                            Runtime,
                            Control,
                            Actor_id,
                            <<"auto_reconcile"/utf8>>,
                            Now_ms,
                            Status
                        ),
                        Desired,
                        Actor_id,
                        Now_ms,
                        Status
                    )
            end
    end.

-file("src/lightspeed/ops/slo_autopilot.gleam", 405).
-spec rollback_extra_controls(
    runtime(),
    list(control()),
    binary(),
    integer(),
    status()
) -> runtime().
rollback_extra_controls(Runtime, Desired, Actor_id, Now_ms, Status) ->
    rollback_loop(
        active_controls(Runtime),
        Runtime,
        Desired,
        Actor_id,
        Now_ms,
        Status
    ).

-file("src/lightspeed/ops/slo_autopilot.gleam", 165).
?DOC(" Apply one control with an audit trail entry.\n").
-spec apply(runtime(), control(), binary(), binary(), integer(), status()) -> runtime().
apply(Runtime, Control, Actor_id, Reason, At_ms, Status) ->
    case has_control(erlang:element(3, Runtime), Control) of
        true ->
            record_audit(
                Runtime,
                {audit_entry,
                    Actor_id,
                    Control,
                    {noop, <<"already_active"/utf8>>},
                    Reason,
                    At_ms,
                    Status}
            );

        false ->
            _pipe = {runtime,
                erlang:element(2, Runtime),
                [Control | erlang:element(3, Runtime)],
                erlang:element(4, Runtime)},
            record_audit(
                _pipe,
                {audit_entry, Actor_id, Control, applied, Reason, At_ms, Status}
            )
    end.

-file("src/lightspeed/ops/slo_autopilot.gleam", 385).
-spec apply_missing_controls(
    runtime(),
    list(control()),
    binary(),
    integer(),
    status()
) -> runtime().
apply_missing_controls(Runtime, Desired, Actor_id, Now_ms, Status) ->
    case Desired of
        [] ->
            Runtime;

        [Control | Rest] ->
            apply_missing_controls(
                apply(
                    Runtime,
                    Control,
                    Actor_id,
                    <<"auto_reconcile"/utf8>>,
                    Now_ms,
                    Status
                ),
                Rest,
                Actor_id,
                Now_ms,
                Status
            )
    end.

-file("src/lightspeed/ops/slo_autopilot.gleam", 373).
-spec reconcile_controls(
    runtime(),
    list(control()),
    binary(),
    integer(),
    status()
) -> runtime().
reconcile_controls(Runtime, Desired, Actor_id, Now_ms, Status) ->
    Applied = apply_missing_controls(Runtime, Desired, Actor_id, Now_ms, Status),
    rollback_extra_controls(Applied, Desired, Actor_id, Now_ms, Status).

-file("src/lightspeed/ops/slo_autopilot.gleam", 459).
-spec hotspot_control(binary()) -> list(control()).
hotspot_control(Tenant_id) ->
    case Tenant_id of
        <<""/utf8>> ->
            [];

        _ ->
            [{isolate_hotspot_tenant, Tenant_id}]
    end.

-file("src/lightspeed/ops/slo_autopilot.gleam", 526).
-spec append(list(control()), list(control())) -> list(control()).
append(Left, Right) ->
    case Left of
        [] ->
            Right;

        [Entry | Rest] ->
            [Entry | append(Rest, Right)]
    end.

-file("src/lightspeed/ops/slo_autopilot.gleam", 149).
?DOC(" Desired controls for one sample/status pair.\n").
-spec recommended_controls(burn_rate_sample(), status()) -> list(control()).
recommended_controls(Sample, Status) ->
    case Status of
        healthy ->
            [];

        warning ->
            _pipe = [{shed_events, 15}, pause_background_jobs],
            append(_pipe, hotspot_control(erlang:element(6, Sample)));

        critical ->
            _pipe@1 = [{shed_events, 40},
                pause_background_jobs,
                enter_read_only_mode],
            append(_pipe@1, hotspot_control(erlang:element(6, Sample)))
    end.

-file("src/lightspeed/ops/slo_autopilot.gleam", 131).
?DOC(" Classify one burn-rate sample against thresholds.\n").
-spec classify(burn_rate_sample(), thresholds()) -> status().
classify(Sample, Thresholds) ->
    case (erlang:element(4, Sample) >= erlang:element(4, Thresholds)) orelse (erlang:element(
        5,
        Sample
    )
    >= erlang:element(5, Thresholds)) of
        true ->
            critical;

        false ->
            case (erlang:element(4, Sample) >= erlang:element(2, Thresholds))
            orelse (erlang:element(5, Sample) >= erlang:element(3, Thresholds)) of
                true ->
                    warning;

                false ->
                    healthy
            end
    end.

-file("src/lightspeed/ops/slo_autopilot.gleam", 119).
?DOC(" Evaluate one sample and reconcile active controls.\n").
-spec evaluate(runtime(), burn_rate_sample(), binary(), integer()) -> {runtime(),
    status()}.
evaluate(Runtime, Sample, Actor_id, Now_ms) ->
    Status = classify(Sample, erlang:element(2, Runtime)),
    Desired = recommended_controls(Sample, Status),
    {reconcile_controls(Runtime, Desired, Actor_id, Now_ms, Status), Status}.

-file("src/lightspeed/ops/slo_autopilot.gleam", 244).
?DOC(" Threshold accessor.\n").
-spec runtime_thresholds(runtime()) -> thresholds().
runtime_thresholds(Runtime) ->
    erlang:element(2, Runtime).

-file("src/lightspeed/ops/slo_autopilot.gleam", 254).
?DOC(" Audit entries in stable order.\n").
-spec audit_entries(runtime()) -> list(audit_entry()).
audit_entries(Runtime) ->
    lists:reverse(erlang:element(4, Runtime)).

-file("src/lightspeed/ops/slo_autopilot.gleam", 259).
?DOC(" Return `True` when one control can be rolled back.\n").
-spec reversible(control()) -> boolean().
reversible(Control) ->
    case Control of
        {shed_events, _} ->
            true;

        pause_background_jobs ->
            true;

        {isolate_hotspot_tenant, _} ->
            true;

        enter_read_only_mode ->
            true
    end.

-file("src/lightspeed/ops/slo_autopilot.gleam", 269).
?DOC(" Stable status label.\n").
-spec status_label(status()) -> binary().
status_label(Status) ->
    case Status of
        healthy ->
            <<"healthy"/utf8>>;

        warning ->
            <<"warning"/utf8>>;

        critical ->
            <<"critical"/utf8>>
    end.

-file("src/lightspeed/ops/slo_autopilot.gleam", 278).
?DOC(" Stable control label.\n").
-spec control_label(control()) -> binary().
control_label(Control) ->
    case Control of
        {shed_events, Percent} ->
            <<"shed_events:"/utf8, (erlang:integer_to_binary(Percent))/binary>>;

        pause_background_jobs ->
            <<"pause_background_jobs"/utf8>>;

        {isolate_hotspot_tenant, Tenant_id} ->
            <<"isolate_tenant:"/utf8, Tenant_id/binary>>;

        enter_read_only_mode ->
            <<"enter_read_only_mode"/utf8>>
    end.

-file("src/lightspeed/ops/slo_autopilot.gleam", 288).
?DOC(" Stable decision label.\n").
-spec decision_label(decision()) -> binary().
decision_label(Decision) ->
    case Decision of
        applied ->
            <<"applied"/utf8>>;

        rolled_back ->
            <<"rolled_back"/utf8>>;

        {noop, Reason} ->
            <<"noop:"/utf8, Reason/binary>>
    end.

-file("src/lightspeed/ops/slo_autopilot.gleam", 466).
-spec hotspot_label(binary()) -> binary().
hotspot_label(Tenant_id) ->
    case Tenant_id of
        <<""/utf8>> ->
            <<"none"/utf8>>;

        _ ->
            Tenant_id
    end.

-file("src/lightspeed/ops/slo_autopilot.gleam", 297).
?DOC(" Stable sample label.\n").
-spec sample_label(burn_rate_sample()) -> binary().
sample_label(Sample) ->
    <<<<<<<<<<<<<<<<<<"service="/utf8, (erlang:element(2, Sample))/binary>>/binary,
                                    "|slo="/utf8>>/binary,
                                (erlang:element(3, Sample))/binary>>/binary,
                            "|short="/utf8>>/binary,
                        (erlang:integer_to_binary(erlang:element(4, Sample)))/binary>>/binary,
                    "|long="/utf8>>/binary,
                (erlang:integer_to_binary(erlang:element(5, Sample)))/binary>>/binary,
            "|hotspot_tenant="/utf8>>/binary,
        (hotspot_label(erlang:element(6, Sample)))/binary>>.

-file("src/lightspeed/ops/slo_autopilot.gleam", 311).
?DOC(" Stable threshold label.\n").
-spec thresholds_label(thresholds()) -> binary().
thresholds_label(Thresholds) ->
    <<<<<<<<<<<<<<"warn_short="/utf8,
                                (erlang:integer_to_binary(
                                    erlang:element(2, Thresholds)
                                ))/binary>>/binary,
                            ",warn_long="/utf8>>/binary,
                        (erlang:integer_to_binary(erlang:element(3, Thresholds)))/binary>>/binary,
                    ",critical_short="/utf8>>/binary,
                (erlang:integer_to_binary(erlang:element(4, Thresholds)))/binary>>/binary,
            ",critical_long="/utf8>>/binary,
        (erlang:integer_to_binary(erlang:element(5, Thresholds)))/binary>>.

-file("src/lightspeed/ops/slo_autopilot.gleam", 323).
?DOC(" Stable audit-entry label.\n").
-spec audit_label(audit_entry()) -> binary().
audit_label(Entry) ->
    <<<<<<<<<<<<<<<<<<<<<<"actor="/utf8, (erlang:element(2, Entry))/binary>>/binary,
                                            "|control="/utf8>>/binary,
                                        (control_label(erlang:element(3, Entry)))/binary>>/binary,
                                    "|decision="/utf8>>/binary,
                                (decision_label(erlang:element(4, Entry)))/binary>>/binary,
                            "|reason="/utf8>>/binary,
                        (erlang:element(5, Entry))/binary>>/binary,
                    "|status="/utf8>>/binary,
                (status_label(erlang:element(7, Entry)))/binary>>/binary,
            "|at_ms="/utf8>>/binary,
        (erlang:integer_to_binary(erlang:element(6, Entry)))/binary>>.

-file("src/lightspeed/ops/slo_autopilot.gleam", 339).
?DOC(" Convert one audit entry into a metric point.\n").
-spec audit_metric(audit_entry()) -> lightspeed@ops@telemetry:metric().
audit_metric(Entry) ->
    {counter,
        <<"lightspeed.slo.autopilot_control_total"/utf8>>,
        1,
        [{tag, <<"actor_id"/utf8>>, erlang:element(2, Entry)},
            {tag, <<"control"/utf8>>, control_label(erlang:element(3, Entry))},
            {tag, <<"decision"/utf8>>, decision_label(erlang:element(4, Entry))},
            {tag, <<"status"/utf8>>, status_label(erlang:element(7, Entry))}]}.

-file("src/lightspeed/ops/slo_autopilot.gleam", 508).
-spec audits_reversible(list(audit_entry())) -> boolean().
audits_reversible(Entries) ->
    case Entries of
        [] ->
            true;

        [Entry | Rest] ->
            reversible(erlang:element(3, Entry)) andalso audits_reversible(Rest)
    end.

-file("src/lightspeed/ops/slo_autopilot.gleam", 515).
-spec contains(list(binary()), binary()) -> boolean().
contains(Values, Target) ->
    case Values of
        [] ->
            false;

        [Value | Rest] ->
            case Value =:= Target of
                true ->
                    true;

                false ->
                    contains(Rest, Target)
            end
    end.

-file("src/lightspeed/ops/slo_autopilot.gleam", 495).
-spec unique_controls(list(control()), list(binary())) -> boolean().
unique_controls(Controls, Seen) ->
    case Controls of
        [] ->
            true;

        [Control | Rest] ->
            Label = control_label(Control),
            case contains(Seen, Label) of
                true ->
                    false;

                false ->
                    unique_controls(Rest, [Label | Seen])
            end
    end.

-file("src/lightspeed/ops/slo_autopilot.gleam", 353).
?DOC(" Runtime invariants.\n").
-spec valid(runtime()) -> boolean().
valid(Runtime) ->
    Thresholds = erlang:element(2, Runtime),
    (((((erlang:element(2, Thresholds) > 0) andalso (erlang:element(
        3,
        Thresholds
    )
    > 0))
    andalso (erlang:element(4, Thresholds) >= erlang:element(2, Thresholds)))
    andalso (erlang:element(5, Thresholds) >= erlang:element(3, Thresholds)))
    andalso unique_controls(active_controls(Runtime), []))
    andalso audits_reversible(audit_entries(Runtime)).

-file("src/lightspeed/ops/slo_autopilot.gleam", 533).
-spec join_with(binary(), list(binary())) -> binary().
join_with(Separator, Values) ->
    case Values of
        [] ->
            <<""/utf8>>;

        [Value] ->
            Value;

        [Value@1 | Rest] ->
            <<<<Value@1/binary, Separator/binary>>/binary,
                (join_with(Separator, Rest))/binary>>
    end.

-file("src/lightspeed/ops/slo_autopilot.gleam", 364).
?DOC(" Stable runtime signature for deterministic fixtures.\n").
-spec signature(runtime()) -> binary().
signature(Runtime) ->
    <<<<<<<<<<"thresholds="/utf8,
                        (thresholds_label(erlang:element(2, Runtime)))/binary>>/binary,
                    "|active="/utf8>>/binary,
                (join_with(
                    <<","/utf8>>,
                    gleam@list:map(
                        active_controls(Runtime),
                        fun control_label/1
                    )
                ))/binary>>/binary,
            "|audit="/utf8>>/binary,
        (erlang:integer_to_binary(erlang:length(erlang:element(4, Runtime))))/binary>>.