1 import Base 2 import ../json.bend as Json 3 import ../swlive.bend as L 4 import ../store/base.bend as SBase 5 import ../store/seq.bend as SSeq 6 import ../clock.bend as Clock 7 import ./events.bend as Ev 8 import ./cadence.bend as Cad 9 import ./config.bend as Cfg 10 11 # The decision the daemon acts on, what a tick answers, and the result 12 # builders for the outcomes that need no switch. Kept apart from `apply` so 13 # the policy side of a tick never imports the switch modules. 14 15 type Dec is Data: 16 DNone{reason: String, detail: String} 17 DExhausted{earliest: Maybe<&2, U32>} 18 DGo{to: String, to_email: String, trigger: String, left_headroom: Maybe<&2, U32>, left_recovery: Maybe<&2, U32>} 19 20 # The events of one tick, in order, and the sleeper's facts. 21 type TickRes is Data: 22 TickRes{info: Cad.Info, events: List<&2, Json.Json>} 23 24 # What `tick` was handed besides the decision. 25 type Ctx is Data: 26 Ctx{ctx: SBase.Ctx, dry_run: Bool, cfg: Cfg.Cfg, now: U32, ts: String, opts: L.Opts, active: Json.Json, next_poll: Maybe<&2, U32>} 27 28 def sel_o(+c: Bool, x: Cad.Outcome, y: Cad.Outcome) -> Cad.Outcome: 29 match c: 30 case True{}: 31 x 32 case False{}: 33 y 34 35 def blocked_reason(+r: String) -> Bool: 36 String.eq(r, "no-candidates") || String.eq(r, "no-comparison") || String.eq(r, "no-qualifying-candidate") || String.eq(r, "no-viable-target") || String.eq(r, "all-exhausted") 37 38 def info(o: Cad.Outcome, +until: Maybe<&2, U32>, +long: Bool, +idle: Bool, +np: Maybe<&2, U32>) -> Cad.Info: 39 Cad.Info{o, until, long, idle, np} 40 41 def one(i: Cad.Info, +e: Json.Json) -> TickRes: 42 TickRes{i, [e]} 43 44 def iso_opt(+m: Maybe<&2, U32>) -> Maybe<&2, String>: 45 match m: 46 case None{}: 47 None{} 48 case Some{t}: 49 Some{Clock.iso8601_utc(t)} 50 51 def ctx_ts(+c: Ctx) -> String: 52 match c: 53 case Ctx{x, d, cf, n, ts, o, a, np}: 54 ts 55 56 def ctx_np(+c: Ctx) -> Maybe<&2, U32>: 57 match c: 58 case Ctx{x, d, cf, n, ts, o, a, np}: 59 np 60 61 def none_res(+c: Ctx, +reason: String, +detail: String) -> TickRes: 62 one(info(sel_o(blocked_reason(reason), Cad.Blocked{}, Cad.NoAction{}), None{}, String.eq(reason, "no-candidates"), String.eq(reason, "active-idle"), ctx_np(c)), Ev.no_switch(ctx_ts(c), reason, detail)) 63 64 def exhausted_res(+c: Ctx, +earliest: Maybe<&2, U32>) -> TickRes: 65 one(info(Cad.Blocked{}, earliest, False{}, False{}, ctx_np(c)), Ev.all_exhausted(ctx_ts(c), iso_opt(earliest))) 66 67 # A failed tick: the error as an event, exit 1. 68 def error_res(+c: Ctx, +msg: String) -> TickRes: 69 one(info(Cad.Failed{}, None{}, False{}, False{}, ctx_np(c)), Ev.error(ctx_ts(c), msg, True{})) 70 71 # ---- the switch itself ---------------------------------------------------------------------- 72 73 def ctx_now(+c: Ctx) -> U32: 74 match c: 75 case Ctx{x, d, cf, n, ts, o, a, np}: 76 n 77 78 def ctx_cfg(+c: Ctx) -> Cfg.Cfg: 79 match c: 80 case Ctx{x, d, cf, n, ts, o, a, np}: 81 cf 82 83 def ctx_store(+c: Ctx) -> SBase.Ctx: 84 match c: 85 case Ctx{x, d, cf, n, ts, o, a, np}: 86 x 87 88 def ctx_opts(+c: Ctx) -> L.Opts: 89 match c: 90 case Ctx{x, d, cf, n, ts, o, a, np}: 91 o 92 93 def ctx_active(+c: Ctx) -> Json.Json: 94 match c: 95 case Ctx{x, d, cf, n, ts, o, a, np}: 96 a 97 98 def ctx_dry(+c: Ctx) -> Bool: 99 match c: 100 case Ctx{x, d, cf, n, ts, o, a, np}: 101 d