meerkat-machine-schema 0.5.2

Formal machine schemas and transition definitions for Meerkat
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
use indexmap::IndexMap;

use crate::{
    EffectDisposition, EffectDispositionRule, EffectEmit, EnumSchema, Expr, FieldSchema,
    HelperSchema, InitSchema, InputMatch, MachineSchema, RustBinding, StateSchema,
    TransitionSchema, TypeRef, VariantSchema,
};

pub fn peer_comms_machine() -> MachineSchema {
    MachineSchema {
        machine: "PeerCommsMachine".into(),
        version: 2,
        rust: RustBinding {
            crate_name: "meerkat-comms".into(),
            module: "generated::peer_comms".into(),
        },
        state: StateSchema {
            phase: EnumSchema {
                name: "PeerCommsPhase".into(),
                variants: vec![variant("Ready")],
            },
            fields: vec![],
            init: InitSchema {
                phase: "Ready".into(),
                fields: vec![],
            },
            terminal_phases: vec![],
        },
        inputs: EnumSchema {
            name: "PeerCommsInput".into(),
            variants: vec![
                VariantSchema {
                    name: "ClassifyExternalEnvelope".into(),
                    fields: vec![
                        field("raw_item_id", TypeRef::Named("RawItemId".into())),
                        field("require_peer_auth", TypeRef::Bool),
                        field("sender_name_known", TypeRef::Bool),
                        field("sender_name", TypeRef::String),
                        field("fallback_sender_name", TypeRef::String),
                        field("kind", TypeRef::Enum("PeerEnvelopeKind".into())),
                        field("intent", TypeRef::String),
                        field("lifecycle_peer_present", TypeRef::Bool),
                        field("lifecycle_peer", TypeRef::String),
                        field("handling_mode_present", TypeRef::Bool),
                        field("handling_mode", TypeRef::Named("HandlingMode".into())),
                        field("silent_intent", TypeRef::Bool),
                        field("dismiss_message", TypeRef::Bool),
                    ],
                },
                VariantSchema {
                    name: "ClassifyPlainEvent".into(),
                    fields: vec![
                        field("raw_item_id", TypeRef::Named("RawItemId".into())),
                        field("source_name", TypeRef::String),
                        field("handling_mode", TypeRef::Named("HandlingMode".into())),
                    ],
                },
            ],
        },
        effects: EnumSchema {
            name: "PeerCommsEffect".into(),
            variants: vec![
                variant("DropIngress"),
                variant("SetDismissFlag"),
                VariantSchema {
                    name: "EnqueueClassifiedEntry".into(),
                    fields: vec![
                        field("raw_item_id", TypeRef::Named("RawItemId".into())),
                        field("class", TypeRef::Named("PeerInputClass".into())),
                        field("from_peer", TypeRef::Option(Box::new(TypeRef::String))),
                        field("lifecycle_peer", TypeRef::Option(Box::new(TypeRef::String))),
                        field(
                            "normalized_handling_mode",
                            TypeRef::Named("HandlingMode".into()),
                        ),
                    ],
                },
            ],
        },
        helpers: vec![
            HelperSchema {
                name: "EffectiveSender".into(),
                params: vec![
                    field("sender_name_known", TypeRef::Bool),
                    field("sender_name", TypeRef::String),
                    field("fallback_sender_name", TypeRef::String),
                ],
                returns: TypeRef::Option(Box::new(TypeRef::String)),
                body: Expr::IfElse {
                    condition: Box::new(Expr::Binding("sender_name_known".into())),
                    then_expr: Box::new(Expr::Some(Box::new(Expr::Binding("sender_name".into())))),
                    else_expr: Box::new(Expr::Some(Box::new(Expr::Binding(
                        "fallback_sender_name".into(),
                    )))),
                },
            },
            HelperSchema {
                name: "EffectiveLifecyclePeer".into(),
                params: vec![
                    field("lifecycle_peer_present", TypeRef::Bool),
                    field("lifecycle_peer", TypeRef::String),
                    field("sender_name_known", TypeRef::Bool),
                    field("sender_name", TypeRef::String),
                    field("fallback_sender_name", TypeRef::String),
                ],
                returns: TypeRef::Option(Box::new(TypeRef::String)),
                body: Expr::IfElse {
                    condition: Box::new(Expr::Binding("lifecycle_peer_present".into())),
                    then_expr: Box::new(Expr::Some(Box::new(Expr::Binding(
                        "lifecycle_peer".into(),
                    )))),
                    else_expr: Box::new(Expr::Call {
                        helper: "EffectiveSender".into(),
                        args: vec![
                            Expr::Binding("sender_name_known".into()),
                            Expr::Binding("sender_name".into()),
                            Expr::Binding("fallback_sender_name".into()),
                        ],
                    }),
                },
            },
            HelperSchema {
                name: "NormalizedHandlingMode".into(),
                params: vec![
                    field("handling_mode_present", TypeRef::Bool),
                    field("handling_mode", TypeRef::Named("HandlingMode".into())),
                ],
                returns: TypeRef::Named("HandlingMode".into()),
                body: Expr::IfElse {
                    condition: Box::new(Expr::Binding("handling_mode_present".into())),
                    then_expr: Box::new(Expr::Binding("handling_mode".into())),
                    else_expr: Box::new(Expr::NamedVariant {
                        enum_name: "HandlingMode".into(),
                        variant: "Queue".into(),
                    }),
                },
            },
        ],
        derived: vec![],
        invariants: vec![],
        transitions: vec![
            TransitionSchema {
                name: "DropUntrustedExternal".into(),
                from: vec!["Ready".into()],
                on: InputMatch {
                    variant: "ClassifyExternalEnvelope".into(),
                    bindings: vec![
                        "require_peer_auth".into(),
                        "raw_item_id".into(),
                        "sender_name_known".into(),
                        "sender_name".into(),
                        "fallback_sender_name".into(),
                        "kind".into(),
                        "intent".into(),
                        "lifecycle_peer_present".into(),
                        "lifecycle_peer".into(),
                        "handling_mode_present".into(),
                        "handling_mode".into(),
                        "silent_intent".into(),
                        "dismiss_message".into(),
                    ],
                },
                guards: vec![
                    guard_eq("require_peer_auth", Expr::Bool(true)),
                    guard_eq("sender_name_known", Expr::Bool(false)),
                ],
                updates: vec![],
                to: "Ready".into(),
                emit: vec![emit("DropIngress", IndexMap::new())],
            },
            TransitionSchema {
                name: "DropAckExternal".into(),
                from: vec!["Ready".into()],
                on: external_input(),
                guards: vec![guard_eq_variant("kind", "PeerEnvelopeKind", "Ack")],
                updates: vec![],
                to: "Ready".into(),
                emit: vec![emit("DropIngress", IndexMap::new())],
            },
            TransitionSchema {
                name: "DismissExternalMessage".into(),
                from: vec!["Ready".into()],
                on: external_input(),
                guards: vec![
                    guard_not_untrusted_external(),
                    guard_eq_variant("kind", "PeerEnvelopeKind", "Message"),
                    guard_eq("dismiss_message", Expr::Bool(true)),
                ],
                updates: vec![],
                to: "Ready".into(),
                emit: vec![emit("SetDismissFlag", IndexMap::new())],
            },
            TransitionSchema {
                name: "EnqueueLifecycleAdded".into(),
                from: vec!["Ready".into()],
                on: external_input(),
                guards: vec![
                    guard_not_untrusted_external(),
                    guard_eq_variant("kind", "PeerEnvelopeKind", "Request"),
                    guard_eq("intent", Expr::String("mob.peer_added".into())),
                ],
                updates: vec![],
                to: "Ready".into(),
                emit: vec![enqueue_effect("PeerLifecycleAdded", true)],
            },
            TransitionSchema {
                name: "EnqueueLifecycleRetired".into(),
                from: vec!["Ready".into()],
                on: external_input(),
                guards: vec![
                    guard_not_untrusted_external(),
                    guard_eq_variant("kind", "PeerEnvelopeKind", "Request"),
                    guard_eq("intent", Expr::String("mob.peer_retired".into())),
                ],
                updates: vec![],
                to: "Ready".into(),
                emit: vec![enqueue_effect("PeerLifecycleRetired", true)],
            },
            TransitionSchema {
                name: "EnqueueLifecycleUnwired".into(),
                from: vec!["Ready".into()],
                on: external_input(),
                guards: vec![
                    guard_not_untrusted_external(),
                    guard_eq_variant("kind", "PeerEnvelopeKind", "Request"),
                    guard_eq("intent", Expr::String("mob.peer_unwired".into())),
                ],
                updates: vec![],
                to: "Ready".into(),
                emit: vec![enqueue_effect("PeerLifecycleUnwired", true)],
            },
            TransitionSchema {
                name: "EnqueueLifecycleKickoffFailed".into(),
                from: vec!["Ready".into()],
                on: external_input(),
                guards: vec![
                    guard_not_untrusted_external(),
                    guard_eq_variant("kind", "PeerEnvelopeKind", "Request"),
                    guard_eq("intent", Expr::String("mob.kickoff_failed".into())),
                ],
                updates: vec![],
                to: "Ready".into(),
                emit: vec![enqueue_effect("PeerLifecycleKickoffFailed", true)],
            },
            TransitionSchema {
                name: "EnqueueLifecycleKickoffCancelled".into(),
                from: vec!["Ready".into()],
                on: external_input(),
                guards: vec![
                    guard_not_untrusted_external(),
                    guard_eq_variant("kind", "PeerEnvelopeKind", "Request"),
                    guard_eq("intent", Expr::String("mob.kickoff_cancelled".into())),
                ],
                updates: vec![],
                to: "Ready".into(),
                emit: vec![enqueue_effect("PeerLifecycleKickoffCancelled", true)],
            },
            TransitionSchema {
                name: "EnqueueSilentRequest".into(),
                from: vec!["Ready".into()],
                on: external_input(),
                guards: vec![
                    guard_not_untrusted_external(),
                    guard_eq_variant("kind", "PeerEnvelopeKind", "Request"),
                    guard_eq("silent_intent", Expr::Bool(true)),
                    guard_not_mob_lifecycle_intent(),
                ],
                updates: vec![],
                to: "Ready".into(),
                emit: vec![enqueue_effect("SilentRequest", false)],
            },
            TransitionSchema {
                name: "EnqueueActionableRequest".into(),
                from: vec!["Ready".into()],
                on: external_input(),
                guards: vec![
                    guard_not_untrusted_external(),
                    guard_eq_variant("kind", "PeerEnvelopeKind", "Request"),
                    guard_eq("silent_intent", Expr::Bool(false)),
                    guard_not_mob_lifecycle_intent(),
                ],
                updates: vec![],
                to: "Ready".into(),
                emit: vec![enqueue_effect("ActionableRequest", false)],
            },
            TransitionSchema {
                name: "EnqueueActionableMessage".into(),
                from: vec!["Ready".into()],
                on: external_input(),
                guards: vec![
                    guard_not_untrusted_external(),
                    guard_eq_variant("kind", "PeerEnvelopeKind", "Message"),
                    guard_eq("dismiss_message", Expr::Bool(false)),
                ],
                updates: vec![],
                to: "Ready".into(),
                emit: vec![enqueue_effect("ActionableMessage", false)],
            },
            TransitionSchema {
                name: "EnqueueResponse".into(),
                from: vec!["Ready".into()],
                on: external_input(),
                guards: vec![
                    guard_not_untrusted_external(),
                    guard_eq_variant("kind", "PeerEnvelopeKind", "Response"),
                ],
                updates: vec![],
                to: "Ready".into(),
                emit: vec![enqueue_effect("Response", false)],
            },
            TransitionSchema {
                name: "EnqueuePlainEvent".into(),
                from: vec!["Ready".into()],
                on: InputMatch {
                    variant: "ClassifyPlainEvent".into(),
                    bindings: vec![
                        "raw_item_id".into(),
                        "source_name".into(),
                        "handling_mode".into(),
                    ],
                },
                guards: vec![],
                updates: vec![],
                to: "Ready".into(),
                emit: vec![EffectEmit {
                    variant: "EnqueueClassifiedEntry".into(),
                    fields: IndexMap::from([
                        ("raw_item_id".into(), Expr::Binding("raw_item_id".into())),
                        (
                            "class".into(),
                            Expr::NamedVariant {
                                enum_name: "PeerInputClass".into(),
                                variant: "PlainEvent".into(),
                            },
                        ),
                        ("from_peer".into(), Expr::None),
                        ("lifecycle_peer".into(), Expr::None),
                        (
                            "normalized_handling_mode".into(),
                            Expr::Binding("handling_mode".into()),
                        ),
                    ]),
                }],
            },
        ],
        ci_step_limit: None,
        effect_dispositions: vec![
            disposition("DropIngress", EffectDisposition::External),
            disposition("SetDismissFlag", EffectDisposition::Local),
            disposition("EnqueueClassifiedEntry", EffectDisposition::External),
        ],
    }
}

fn external_input() -> InputMatch {
    InputMatch {
        variant: "ClassifyExternalEnvelope".into(),
        bindings: vec![
            "raw_item_id".into(),
            "require_peer_auth".into(),
            "sender_name_known".into(),
            "sender_name".into(),
            "fallback_sender_name".into(),
            "kind".into(),
            "intent".into(),
            "lifecycle_peer_present".into(),
            "lifecycle_peer".into(),
            "handling_mode_present".into(),
            "handling_mode".into(),
            "silent_intent".into(),
            "dismiss_message".into(),
        ],
    }
}

fn enqueue_effect(class_variant: &str, lifecycle: bool) -> EffectEmit {
    let lifecycle_peer = if lifecycle {
        Expr::Call {
            helper: "EffectiveLifecyclePeer".into(),
            args: vec![
                Expr::Binding("lifecycle_peer_present".into()),
                Expr::Binding("lifecycle_peer".into()),
                Expr::Binding("sender_name_known".into()),
                Expr::Binding("sender_name".into()),
                Expr::Binding("fallback_sender_name".into()),
            ],
        }
    } else {
        Expr::None
    };
    EffectEmit {
        variant: "EnqueueClassifiedEntry".into(),
        fields: IndexMap::from([
            ("raw_item_id".into(), Expr::Binding("raw_item_id".into())),
            (
                "class".into(),
                Expr::NamedVariant {
                    enum_name: "PeerInputClass".into(),
                    variant: class_variant.into(),
                },
            ),
            (
                "from_peer".into(),
                Expr::Call {
                    helper: "EffectiveSender".into(),
                    args: vec![
                        Expr::Binding("sender_name_known".into()),
                        Expr::Binding("sender_name".into()),
                        Expr::Binding("fallback_sender_name".into()),
                    ],
                },
            ),
            ("lifecycle_peer".into(), lifecycle_peer),
            (
                "normalized_handling_mode".into(),
                Expr::Call {
                    helper: "NormalizedHandlingMode".into(),
                    args: vec![
                        Expr::Binding("handling_mode_present".into()),
                        Expr::Binding("handling_mode".into()),
                    ],
                },
            ),
        ]),
    }
}

fn variant(name: &str) -> VariantSchema {
    VariantSchema {
        name: name.into(),
        fields: vec![],
    }
}

fn field(name: &str, ty: TypeRef) -> FieldSchema {
    FieldSchema {
        name: name.into(),
        ty,
    }
}

fn guard_eq(binding: &str, expr: Expr) -> crate::Guard {
    crate::Guard {
        name: format!("{binding}_matches"),
        expr: Expr::Eq(Box::new(Expr::Binding(binding.into())), Box::new(expr)),
    }
}

fn guard_not_untrusted_external() -> crate::Guard {
    crate::Guard {
        name: "not_untrusted_external".into(),
        expr: Expr::Not(Box::new(Expr::And(vec![
            Expr::Eq(
                Box::new(Expr::Binding("require_peer_auth".into())),
                Box::new(Expr::Bool(true)),
            ),
            Expr::Eq(
                Box::new(Expr::Binding("sender_name_known".into())),
                Box::new(Expr::Bool(false)),
            ),
        ]))),
    }
}

fn guard_not_mob_lifecycle_intent() -> crate::Guard {
    crate::Guard {
        name: "not_mob_lifecycle_intent".into(),
        expr: Expr::And(vec![
            Expr::Neq(
                Box::new(Expr::Binding("intent".into())),
                Box::new(Expr::String("mob.peer_added".into())),
            ),
            Expr::Neq(
                Box::new(Expr::Binding("intent".into())),
                Box::new(Expr::String("mob.peer_retired".into())),
            ),
            Expr::Neq(
                Box::new(Expr::Binding("intent".into())),
                Box::new(Expr::String("mob.peer_unwired".into())),
            ),
            Expr::Neq(
                Box::new(Expr::Binding("intent".into())),
                Box::new(Expr::String("mob.kickoff_failed".into())),
            ),
            Expr::Neq(
                Box::new(Expr::Binding("intent".into())),
                Box::new(Expr::String("mob.kickoff_cancelled".into())),
            ),
        ]),
    }
}

fn disposition(name: &str, disposition: EffectDisposition) -> EffectDispositionRule {
    EffectDispositionRule {
        effect_variant: name.into(),
        disposition,
        handoff_protocol: None,
    }
}

fn guard_eq_variant(binding: &str, enum_name: &str, variant: &str) -> crate::Guard {
    guard_eq(
        binding,
        Expr::NamedVariant {
            enum_name: enum_name.into(),
            variant: variant.into(),
        },
    )
}

fn emit(variant: &str, fields: IndexMap<String, Expr>) -> crate::EffectEmit {
    crate::EffectEmit {
        variant: variant.into(),
        fields,
    }
}