daemon/apply.bend

cswap-bend-cli@0.1.0.0 · 135 lines · 23 definitions

1import Base
2import cswap-bend-cli@0.1.0.0/json.bend as Json
3import cswap-bend-cli@0.1.0.0/act.bend as Act
4import cswap-bend-cli@0.1.0.0/fs.bend as Fs
5import cswap-bend-cli@0.1.0.0/clock.bend as Clock
6import cswap-bend-cli@0.1.0.0/store.bend as Store
7import cswap-bend-cli@0.1.0.0/store/base.bend as SBase
8import cswap-bend-cli@0.1.0.0/store/seq.bend as SSeq
9import cswap-bend-cli@0.1.0.0/swlive.bend as L
10import cswap-bend-cli@0.1.0.0/switch.bend as Sw
11import cswap-bend-cli@0.1.0.0/swbase.bend as SwB
12import cswap-bend-cli@0.1.0.0/daemon/dec.bend as Dc
13import cswap-bend-cli@0.1.0.0/daemon/events.bend as Ev
14import cswap-bend-cli@0.1.0.0/daemon/cadence.bend as Cad
15import cswap-bend-cli@0.1.0.0/daemon/state.bend as St
16import cswap-bend-cli@0.1.0.0/daemon/config.bend as Cfg
17
18# Acting on a decision: print the event, and for a switch run it under the
19# autoswitch state lock (cooldown re-checked there, as `_perform` does),
20# record it, and say what the sleeper needs to know. A switch that fails
21# becomes an `error` event, never a crash.
22
23# What the policy decided, in the units the daemon acts on. Headroom is
24# centi-percent, times unix seconds.
25
26def ActM(-A: Type) -> Type:
27  Act.of(A)
28
29def ActM.pure(-A: Type, x: A) -> Act.of(A):
30  Act.pure(A, x)
31
32def ActM.bind(-A: Type, -B: Type, m: Act.of(A), f: A -> Act.of(B)) -> Act.of(B):
33  Act.bind(A, B, m, f)
34
35def warnings_of(+w: Json.Json) -> List<&2, String>:
36  SwB.texts_of(w)
37
38def switched_res(+c: Dc.Ctx, +trigger: String, +r: Json.Json) -> Dc.TickRes:
39  Dc.one(Dc.info(Cad.Switched{}, None{}, False{}, False{}, Dc.ctx_np(c)), Ev.switch(Dc.ctx_ts(c), trigger, SSeq.get_or("from", r, Json.JNull{}), SSeq.get_or("to", r, Json.JNull{}), warnings_of(SSeq.get_or("warnings", r, Json.JNull{})), False{}))
40
41def num_of(+m: Maybe<&2, Json.Json>) -> Maybe<&2, U32>:
42  match m:
43    case Some{j}:
44      Json.as_u32(j)
45    case None{}:
46      None{}
47
48def from_number(+r: Json.Json) -> Maybe<&2, U32>:
49  num_of(Json.get("number", SSeq.get_or("from", r, Json.JNull{})))
50
51def record_sw(+c: Dc.Ctx, +root: String, +to: String, +trigger: String, +lh: Maybe<&2, U32>, +lr: Maybe<&2, U32>, +r: Json.Json) -> Act.of(Dc.TickRes):
52  do ActM<Dc.TickRes>:
53    st : Json.Json <- Act.lift(Json.Json, St.read_state(root))
54    St.write_state(root, St.record_switch(st, Dc.ctx_now(c), to, from_number(r), lh, lr, trigger))
55    return switched_res(c, trigger, r)
56
57def after_switch2(+switched: Bool, +c: Dc.Ctx, +root: String, +to: String, +trigger: String, +lh: Maybe<&2, U32>, +lr: Maybe<&2, U32>, +r: Json.Json) -> Act.of(Dc.TickRes):
58  match switched:
59    case False{}:
60      Act.pure(Dc.TickRes, Dc.none_res(c, "already-active", SSeq.field_str(r, "reason")))
61    case True{}:
62      record_sw(c, root, to, trigger, lh, lr, r)
63
64def bool_of(+m: Maybe<&2, Json.Json>) -> Bool:
65  match m:
66    case Some{Json.JBool{b}}:
67      b
68    case _:
69      False{}
70
71def is_switched(+r: Json.Json) -> Bool:
72  bool_of(Json.get("switched", r))
73
74def after_switch(+c: Dc.Ctx, +root: String, go: Dc.Dec, +r: Json.Json) -> Act.of(Dc.TickRes):
75  match go:
76    case Dc.DGo{to, email, trigger, lh, lr}:
77      after_switch2(is_switched(r), c, root, to, trigger, lh, lr, r)
78    case _:
79      Act.pure(Dc.TickRes, Dc.error_res(c, "internal: not a switch"))
80
81def cooldown_applies(+trigger: String) -> Bool:
82  String.eq(trigger, "proactive") || String.eq(trigger, "consume-first")
83
84def cfg_cooldown(+cf: Cfg.Cfg) -> U32:
85  match cf:
86    case Cfg.Cfg{th, iv, cd, hy, st, ia, ut, ms}:
87      cd
88
89def run_switch(+c: Dc.Ctx, +root: String, go: Dc.Dec, +to: String, +trigger: String) -> Act.of(Dc.TickRes):
90  do ActM<Dc.TickRes>:
91    r : Json.Json <- Sw.switch_to(Dc.ctx_store(c), to, False{}, Dc.ctx_opts(c))
92    after_switch(c, root, go, r)
93
94def locked_switch2(+cool: Bool, +c: Dc.Ctx, +root: String, go: Dc.Dec, +to: String, +trigger: String) -> Act.of(Dc.TickRes):
95  Act.cond(Dc.TickRes, cool, Act.pure(Dc.TickRes, Dc.none_res(c, "cooldown", "")), run_switch(c, root, go, to, trigger))
96
97def in_cool(+c: Dc.Ctx, +st: Json.Json, +trigger: String) -> Bool:
98  cooldown_applies(trigger) && St.in_cooldown(st, Dc.ctx_now(c), cfg_cooldown(Dc.ctx_cfg(c)))
99
100def locked_switch(+c: Dc.Ctx, +root: String, go: Dc.Dec, +to: String, +trigger: String) -> Act.of(Dc.TickRes):
101  do ActM<Dc.TickRes>:
102    st : Json.Json <- Act.lift(Json.Json, St.read_state(root))
103    locked_switch2(in_cool(c, st, trigger), c, root, go, to, trigger)
104
105def dry_res(+c: Dc.Ctx, +trigger: String, +to: String, +email: String) -> Dc.TickRes:
106  Dc.one(Dc.info(Cad.Switched{}, None{}, False{}, False{}, Dc.ctx_np(c)), Ev.switch(Dc.ctx_ts(c), trigger, Dc.ctx_active(c), Ev.ref(to, email), [], True{}))
107
108def go_res(+c: Dc.Ctx, go: Dc.Dec) -> Act.of(Dc.TickRes):
109  match go:
110    case Dc.DGo{+to, +email, +trigger, lh, lr}:
111      Act.cond(Dc.TickRes, Dc.ctx_dry(c), Act.pure(Dc.TickRes, dry_res(c, trigger, to, email)),
112        St.with_state_lock(Dc.TickRes, SBase.root_of(Dc.ctx_store(c)), locked_switch(c, SBase.root_of(Dc.ctx_store(c)), go, to, trigger)))
113    case _:
114      Act.pure(Dc.TickRes, Dc.error_res(c, "internal: not a switch"))
115
116# A failed switch is an error event, not a crashed tick.
117def recover(+c: Dc.Ctx, r: Fs.Res(Dc.TickRes)) -> Act.of(Dc.TickRes):
118  match r:
119    case Done{t}:
120      Act.pure(Dc.TickRes, t)
121    case Fail{(code, msg)}:
122      Act.pure(Dc.TickRes, Dc.error_res(c, msg))
123
124def go_safe(+c: Dc.Ctx, go: Dc.Dec) -> Act.of(Dc.TickRes):
125  IO.bind(Fs.Res(Dc.TickRes), Fs.Res(Dc.TickRes), go_res(c, go), r => recover(c, r))
126
127# Act on `d`.
128def apply(+c: Dc.Ctx, d: Dc.Dec) -> Act.of(Dc.TickRes):
129  match d:
130    case Dc.DNone{reason, detail}:
131      Act.pure(Dc.TickRes, Dc.none_res(c, reason, detail))
132    case Dc.DExhausted{earliest}:
133      Act.pure(Dc.TickRes, Dc.exhausted_res(c, earliest))
134    case Dc.DGo{to, email, trigger, lh, lr}:
135      go_safe(c, d)