domain/architecture/ops.bend

near-v-framework@0.3.0.1 · 661 lines · 0 definitions

1import Base
2import near-v-framework@0.3.0.1/general/rose/type.bend as R
3import near-v-framework@0.3.0.1/general/rose/ops.bend as RO
4import near-v-framework@0.3.0.1/general/compound/type.bend as C
5import near-v-framework@0.3.0.1/general/compound/ops.bend as CO
6import near-v-framework@0.3.0.1/general/compound/edge/type.bend as E
7import near-v-framework@0.3.0.1/domain/architecture/relationship/type.bend as Rel
8import near-v-framework@0.3.0.1/domain/architecture/system/type.bend as S
9import near-v-framework@0.3.0.1/domain/architecture/system/container/type.bend as Ct
10import near-v-framework@0.3.0.1/domain/architecture/system/container/component/type.bend as Co
11import near-v-framework@0.3.0.1/domain/architecture/system/deployment/type.bend as D
12import near-v-framework@0.3.0.1/domain/architecture/environment/type.bend as En
13import near-v-framework@0.3.0.1/domain/architecture/environment/party/type.bend as P
14import near-v-framework@0.3.0.1/domain/architecture/environment/external/type.bend as X
15import near-v-framework@0.3.0.1/domain/architecture/environment/constraint/type.bend as K
16import near-v-framework@0.3.0.1/domain/architecture/system/release/ops.bend as RlO
17import near-v-framework@0.3.0.1/domain/architecture/category/type.bend as K2
18import near-v-framework@0.3.0.1/domain/architecture/environment/inventory/type.bend as I
19import near-v-framework@0.3.0.1/domain/architecture/environment/inventory/ops.bend as InvO
20import near-v-framework@0.3.0.1/domain/architecture/environment/agreement/type.bend as G
21import near-v-framework@0.3.0.1/domain/architecture/system/deployment/host/type.bend as H
22import near-v-framework@0.3.0.1/domain/architecture/system/deployment/nix_darwin/ops.bend as NDO
23import near-v-framework@0.3.0.1/domain/architecture/system/deployment/nix_darwin/type.bend as ND
24import near-v-framework@0.3.0.1/domain/architecture/system/deployment/nix_darwin/setting/ops.bend as StO
25import near-v-framework@0.3.0.1/domain/architecture/type.bend as A
26
27# ---- the architecture as a compound graph: the system's tree, the environment's leaves, and every
28# element's relationships as edges from it ----
29def uses_edges(+src: String, us: List<&2, Rel.Relationship>) -> List<&2, E.Edge>:
30  match us:
31    case []:
32      []
33    case u <> rest:
34      match u:
35        case Rel.Relationship{d, desc, t, _}:
36          E.Edge{src, d, desc, t} <> uses_edges(src, rest)
37
38def language_name(l: Co.Language) -> String:
39  match l:
40    case Co.Bend{}:
41      "Bend 2"
42    case Co.C{}:
43      "C"
44    case Co.Rust{}:
45      "Rust"
46    case Co.TypeScript{}:
47      "TypeScript"
48    case Co.JavaScript{}:
49      "JavaScript"
50    case Co.Python{}:
51      "Python"
52    case Co.Go{}:
53      "Go"
54    case Co.Opaque{}:
55      ""
56
57# A container's technology, as drawn: its frameworks and languages, else its vendors.
58def technology(+vs: List<&2, String>, +fs: List<&2, String>, +ls: List<&2, String>) -> String:
59  Bool.pick(String, List.is_empty(&2, String, List.append(&2, String, fs, ls)), String.join(vs, ", "), String.join(List.append(&2, String, fs, ls), ", "))
60
61def component_roses(cs: List<&2, Co.Component>) -> List<&2, R.Rose<A.Element>>:
62  match cs:
63    case []:
64      []
65    case c <> rest:
66      match c:
67        case Co.Component{k, n, d, l, _}:
68          R.Rose{k, A.Element{n, d, language_name(l), A.ComponentLevel{}}, []} <> component_roses(rest)
69
70def component_edges(cs: List<&2, Co.Component>) -> List<&2, E.Edge>:
71  match cs:
72    case []:
73      []
74    case c <> rest:
75      match c:
76        case Co.Component{k, _, _, _, us}:
77          List.append(&2, E.Edge, uses_edges(k, us), component_edges(rest))
78
79def container_roses(cs: List<&2, Ct.Container>) -> List<&2, R.Rose<A.Element>>:
80  match cs:
81    case []:
82      []
83    case c <> rest:
84      match c:
85        case Ct.Container{k, n, d, _, _, _, _, _, vs, fs, _, ls, _, comps, _}:
86          R.Rose{k, A.Element{n, d, technology(vs, fs, ls), A.ContainerLevel{}}, component_roses(comps)} <> container_roses(rest)
87
88def container_edges(cs: List<&2, Ct.Container>) -> List<&2, E.Edge>:
89  match cs:
90    case []:
91      []
92    case c <> rest:
93      match c:
94        case Ct.Container{+k, _, _, _, _, _, _, _, _, _, _, _, _, comps, us}:
95          List.append(&2, E.Edge, uses_edges(k, us), List.append(&2, E.Edge, component_edges(comps), container_edges(rest)))
96
97def party_rose(p: P.Party) -> R.Rose<A.Element>:
98  match p:
99    case P.Human{k, n, d, _}:
100      R.Rose{k, A.Element{n, d, "", A.PersonLevel{}}, []}
101    case P.Organization{k, n, d, _, _}:
102      R.Rose{k, A.Element{n, d, "", A.PersonLevel{}}, []}
103    case P.Team{k, n, d, _, _}:
104      R.Rose{k, A.Element{n, d, "", A.PersonLevel{}}, []}
105
106def party_edges(p: P.Party) -> List<&2, E.Edge>:
107  match p:
108    case P.Human{k, _, _, us}:
109      uses_edges(k, us)
110    case P.Organization{k, _, _, _, us}:
111      uses_edges(k, us)
112    case P.Team{k, _, _, _, us}:
113      uses_edges(k, us)
114
115def parties_roses(ps: List<&2, P.Party>) -> List<&2, R.Rose<A.Element>>:
116  match ps:
117    case []:
118      []
119    case p <> rest:
120      party_rose(p) <> parties_roses(rest)
121
122def parties_edges(ps: List<&2, P.Party>) -> List<&2, E.Edge>:
123  match ps:
124    case []:
125      []
126    case p <> rest:
127      List.append(&2, E.Edge, party_edges(p), parties_edges(rest))
128
129def external_roses(xs: List<&2, X.External>) -> List<&2, R.Rose<A.Element>>:
130  match xs:
131    case []:
132      []
133    case x <> rest:
134      match x:
135        case X.External{k, n, d, _, _, _, _, _}:
136          R.Rose{k, A.Element{n, d, "", A.ExternalLevel{}}, []} <> external_roses(rest)
137
138def external_edges(xs: List<&2, X.External>) -> List<&2, E.Edge>:
139  match xs:
140    case []:
141      []
142    case x <> rest:
143      match x:
144        case X.External{k, _, _, _, _, _, _, us}:
145          List.append(&2, E.Edge, uses_edges(k, us), external_edges(rest))
146
147def with_env(sys: R.Rose<A.Element>, +cs: List<&2, Ct.Container>, env: En.Environment) -> C.Compound<A.Element>:
148  match env:
149    case En.Environment{+ps, +xs, _, _, _}:
150      C.Compound{sys <> List.append(&2, R.Rose<A.Element>, parties_roses(ps), external_roses(xs)),
151        List.append(&2, E.Edge, container_edges(cs), List.append(&2, E.Edge, parties_edges(ps), external_edges(xs)))}
152
153def to_compound(a: A.Architecture) -> C.Compound<A.Element>:
154  match a:
155    case A.Architecture{s, env}:
156      match s:
157        case S.System{k, n, d, +cs, _, _}:
158          with_env(R.Rose{k, A.Element{n, d, "", A.SystemLevel{}}, container_roses(cs)}, cs, env)
159
160# ---- looking things up ----
161def system(a: A.Architecture) -> S.System:
162  match a:
163    case A.Architecture{s, _}:
164      s
165
166def system_key(a: A.Architecture) -> String:
167  match a:
168    case A.Architecture{s, _}:
169      match s:
170        case S.System{k, _, _, _, _, _}:
171          k
172
173def containers(a: A.Architecture) -> List<&2, Ct.Container>:
174  match a:
175    case A.Architecture{s, _}:
176      match s:
177        case S.System{_, _, _, cs, _, _}:
178          cs
179
180def deployment(a: A.Architecture) -> D.Deployment:
181  match a:
182    case A.Architecture{s, _}:
183      match s:
184        case S.System{_, _, _, _, dep, _}:
185          dep
186
187def deployment_nodes(d: D.Deployment) -> List<&2, D.Node>:
188  match d:
189    case D.Deployment{_, _, _, ns}:
190      ns
191
192# The machines, nested.
193def nodes(a: A.Architecture) -> List<&2, D.Node>:
194  deployment_nodes(deployment(a))
195
196def keys(a: A.Architecture) -> List<&2, String>:
197  CO.keys(A.Element, to_compound(a))
198
199def container_keys(cs: List<&2, Ct.Container>) -> List<&2, String>:
200  match cs:
201    case []:
202      []
203    case c <> rest:
204      match c:
205        case Ct.Container{k, _, _, _, _, _, _, _, _, _, _, _, _, _, _}:
206          k <> container_keys(rest)
207
208def component_keys(cs: List<&2, Co.Component>) -> List<&2, String>:
209  match cs:
210    case []:
211      []
212    case c <> rest:
213      match c:
214        case Co.Component{k, _, _, _, _}:
215          k <> component_keys(rest)
216
217# Every component key in the system.
218def all_component_keys(cs: List<&2, Ct.Container>) -> List<&2, String>:
219  match cs:
220    case []:
221      []
222    case c <> rest:
223      match c:
224        case Ct.Container{_, _, _, _, _, _, _, _, _, _, _, _, _, comps, _}:
225          List.append(&2, String, component_keys(comps), all_component_keys(rest))
226
227# The components inside one container.
228def components_of(+k: String, cs: List<&2, Ct.Container>) -> List<&2, String>:
229  match cs:
230    case []:
231      []
232    case c <> rest:
233      match c:
234        case Ct.Container{ck, _, _, _, _, _, _, _, _, _, _, _, _, comps, _}:
235          Bool.pick(List<&2, String>, String.eq(k, ck), component_keys(comps), components_of(k, rest))
236
237def environment_keys(a: A.Architecture) -> List<&2, String>:
238  match a:
239    case A.Architecture{_, env}:
240      match env:
241        case En.Environment{ps, xs, _, _, _}:
242          RO.keys(A.Element, List.append(&2, R.Rose<A.Element>, parties_roses(ps), external_roses(xs)))
243
244# ---- well formed: keys are unique, every relationship and every instance names something that exists ----
245def instances(ns: List<&2, D.Node>) -> List<&2, String>:
246  match ns:
247    case []:
248      []
249    case n <> rest:
250      match n:
251        case D.Node{_, _, _, _, _, _, kids, ins}:
252          List.append(&2, String, ins, List.append(&2, String, instances(kids), instances(rest)))
253
254def all_listed(xs: List<&2, String>, +ks: List<&2, String>) -> Bool:
255  match xs:
256    case []:
257      True{}
258    case x <> rest:
259      CO.has(ks, x) && all_listed(rest, ks)
260
261def compound_ok(+c: C.Compound<A.Element>) -> Bool:
262  CO.resolves(A.Element, c) && CO.unique(A.Element, c)
263
264def well_formed(+a: A.Architecture) -> Bool:
265  compound_ok(to_compound(a)) && all_listed(instances(nodes(a)), container_keys(containers(a)))
266
267# ---- only our own containers are opened up into components ----
268def ours(o: Ct.Ownership) -> Bool:
269  match o:
270    case Ct.Ours{}:
271      True{}
272    case Ct.Theirs{}:
273      False{}
274
275def components_ours(cs: List<&2, Ct.Container>) -> Bool:
276  match cs:
277    case []:
278      True{}
279    case c <> rest:
280      match c:
281        case Ct.Container{_, _, _, _, o, _, _, _, _, _, _, _, _, comps, _}:
282          (List.is_empty(&2, Co.Component, comps) || ours(o)) && components_ours(rest)
283
284# ---- on a server, everything is reachable only from the machine itself or over the tailnet ----
285def private(e: Ct.Exposure) -> Bool:
286  match e:
287    case Ct.Local{}:
288      True{}
289    case Ct.Tailnet{}:
290      True{}
291    case _:
292      False{}
293
294def exposure_private(+k: String, cs: List<&2, Ct.Container>) -> Bool:
295  match cs:
296    case []:
297      True{}
298    case c <> rest:
299      match c:
300        case Ct.Container{ck, _, _, _, _, e, _, _, _, _, _, _, _, _, _}:
301          Bool.pick(Bool, String.eq(k, ck), private(e), exposure_private(k, rest))
302
303def all_private(ins: List<&2, String>, +cs: List<&2, Ct.Container>) -> Bool:
304  match ins:
305    case []:
306      True{}
307    case i <> rest:
308      exposure_private(i, cs) && all_private(rest, cs)
309
310def is_server(r: D.Role) -> Bool:
311  match r:
312    case D.Server{}:
313      True{}
314    case D.Device{}:
315      False{}
316
317def nodes_private(ns: List<&2, D.Node>, +cs: List<&2, Ct.Container>) -> Bool:
318  match ns:
319    case []:
320      True{}
321    case n <> rest:
322      match n:
323        case D.Node{_, _, _, r, _, _, kids, ins}:
324          (Bool.not(is_server(r)) || all_private(ins, cs)) && nodes_private(kids, cs) && nodes_private(rest, cs)
325
326def servers_private(+a: A.Architecture) -> Bool:
327  nodes_private(nodes(a), containers(a))
328
329# ---- open source: a system its environment requires to be open source publishes from a public repository ----
330def open_source_required(ks: List<&2, K.Constraint>) -> Bool:
331  match ks:
332    case []:
333      False{}
334    case k <> rest:
335      match k:
336        case K.OpenSource{}:
337          True{}
338        case K.RunsOn{_}:
339          open_source_required(rest)
340
341def open_source(a: A.Architecture) -> Bool:
342  match a:
343    case A.Architecture{s, env}:
344      match s env:
345        case S.System{_, _, _, _, _, r} En.Environment{_, _, ks, _, _}:
346          Bool.not(open_source_required(ks)) || RlO.is_public(r)
347
348# ---- where each key runs: the containers on server nodes and on device nodes ----
349def is_device(r: D.Role) -> Bool:
350  match r:
351    case D.Device{}:
352      True{}
353    case D.Server{}:
354      False{}
355
356def keep_all(c: Bool, xs: List<&2, String>, rest: List<&2, String>) -> List<&2, String>:
357  match c:
358    case True{}:
359      List.append(&2, String, xs, rest)
360    case False{}:
361      rest
362
363def on_servers(ns: List<&2, D.Node>) -> List<&2, String>:
364  match ns:
365    case []:
366      []
367    case n <> rest:
368      match n:
369        case D.Node{_, _, _, r, _, _, kids, ins}:
370          keep_all(is_server(r), ins, List.append(&2, String, on_servers(kids), on_servers(rest)))
371
372def on_devices(ns: List<&2, D.Node>) -> List<&2, String>:
373  match ns:
374    case []:
375      []
376    case n <> rest:
377      match n:
378        case D.Node{_, _, _, r, _, _, kids, ins}:
379          keep_all(is_device(r), ins, List.append(&2, String, on_devices(kids), on_devices(rest)))
380
381# The container a key belongs to: itself, or the one its component sits in, else the key itself.
382def home(+k: String, cs: List<&2, Ct.Container>) -> String:
383  match cs:
384    case []:
385      k
386    case c <> rest:
387      match c:
388        case Ct.Container{+ck, _, _, _, _, _, _, _, _, _, _, _, _, comps, _}:
389          Bool.pick(String, String.eq(k, ck) || CO.has(component_keys(comps), k), ck, home(k, rest))
390
391def exposure_of(+k: String, cs: List<&2, Ct.Container>) -> Ct.Exposure:
392  match cs:
393    case []:
394      Ct.Public{}
395    case c <> rest:
396      match c:
397        case Ct.Container{ck, _, _, _, _, e, _, _, _, _, _, _, _, _, _}:
398          Bool.pick(Ct.Exposure, String.eq(k, ck), e, exposure_of(k, rest))
399
400def is_tailnet(e: Ct.Exposure) -> Bool:
401  match e:
402    case Ct.Tailnet{}:
403      True{}
404    case _:
405      False{}
406
407def is_local(e: Ct.Exposure) -> Bool:
408  match e:
409    case Ct.Local{}:
410      True{}
411    case _:
412      False{}
413
414def edges(a: A.Architecture) -> List<&2, E.Edge>:
415  CO.edges(A.Element, to_compound(a))
416
417# ---- a device reaches a server only over the tailnet ----
418def via_tailnet(es: List<&2, E.Edge>, +cs: List<&2, Ct.Container>, +servers: List<&2, String>, +devices: List<&2, String>) -> Bool:
419  match es:
420    case []:
421      True{}
422    case e <> rest:
423      match e:
424        case E.Edge{x, +y, _, _}:
425          (Bool.not(CO.has(devices, home(x, cs)) && CO.has(servers, home(y, cs))) || is_tailnet(exposure_of(home(y, cs), cs))) &&
426            via_tailnet(rest, cs, servers, devices)
427
428def devices_via_tailnet(+a: A.Architecture) -> Bool:
429  via_tailnet(edges(a), containers(a), on_servers(nodes(a)), on_devices(nodes(a)))
430
431# ---- on a server, a container is on the tailnet exactly when a device reaches it, else local ----
432def reached(+k: String, es: List<&2, E.Edge>, +cs: List<&2, Ct.Container>, +devices: List<&2, String>) -> Bool:
433  match es:
434    case []:
435      False{}
436    case e <> rest:
437      match e:
438        case E.Edge{x, y, _, _}:
439          (CO.has(devices, home(x, cs)) && String.eq(home(y, cs), k)) || reached(k, rest, cs, devices)
440
441def follows_reach(r: Bool, e: Ct.Exposure) -> Bool:
442  match r:
443    case True{}:
444      is_tailnet(e)
445    case False{}:
446      is_local(e)
447
448def reach_ok(ks: List<&2, String>, +a: A.Architecture) -> Bool:
449  match ks:
450    case []:
451      True{}
452    case +k <> rest:
453      follows_reach(reached(k, edges(a), containers(a), on_devices(nodes(a))), exposure_of(k, containers(a))) && reach_ok(rest, a)
454
455def exposure_follows_reach(+a: A.Architecture) -> Bool:
456  reach_ok(on_servers(nodes(a)), a)
457
458# ---- capabilities: who provides each, and how many do ----
459def cap_eq(a: K2.Capability, b: K2.Capability) -> Bool:
460  match a b:
461    case K2.Hosting{} K2.Hosting{}:
462      True{}
463    case K2.Realtime{} K2.Realtime{}:
464      True{}
465    case K2.BlobStore{} K2.BlobStore{}:
466      True{}
467    case K2.Observability{} K2.Observability{}:
468      True{}
469    case _ _:
470      False{}
471
472def has_cap(+want: K2.Capability, cs: List<&2, K2.Capability>) -> Bool:
473  match cs:
474    case []:
475      False{}
476    case c <> rest:
477      cap_eq(want, c) || has_cap(want, rest)
478
479def one_if(b: Bool) -> Nat:
480  match b:
481    case True{}:
482      1n
483    case False{}:
484      0n
485
486def container_providers(+want: K2.Capability, cs: List<&2, Ct.Container>) -> List<&2, String>:
487  match cs:
488    case []:
489      []
490    case c <> rest:
491      match c:
492        case Ct.Container{k, _, _, _, _, _, caps, _, _, _, _, _, _, _, _}:
493          keep_all(has_cap(want, caps), [k], container_providers(want, rest))
494
495def external_providers(+want: K2.Capability, xs: List<&2, X.External>) -> List<&2, String>:
496  match xs:
497    case []:
498      []
499    case x <> rest:
500      match x:
501        case X.External{k, _, _, _, caps, _, _, _}:
502          keep_all(has_cap(want, caps), [k], external_providers(want, rest))
503
504def externals(a: A.Architecture) -> List<&2, X.External>:
505  match a:
506    case A.Architecture{_, env}:
507      match env:
508        case En.Environment{_, xs, _, _, _}:
509          xs
510
511# The containers and outside systems that provide a capability.
512def providers(+want: K2.Capability, +a: A.Architecture) -> List<&2, String>:
513  List.append(&2, String, container_providers(want, containers(a)), external_providers(want, externals(a)))
514
515def provided(+want: K2.Capability, +a: A.Architecture) -> Nat:
516  List.length(&2, String, providers(want, a))
517
518# How many containers host the app privately: Hosting, on the tailnet.
519def private_hosts(cs: List<&2, Ct.Container>) -> Nat:
520  match cs:
521    case []:
522      0n
523    case c <> rest:
524      match c:
525        case Ct.Container{_, _, _, _, _, e, +caps, _, _, _, _, _, _, _, _}:
526          Nat.add(one_if(has_cap(K2.Hosting{}, caps) && is_tailnet(e)), private_hosts(rest))
527
528def private_hosting(+a: A.Architecture) -> Nat:
529  private_hosts(containers(a))
530
531def is_data_api(c: K2.Category) -> Bool:
532  match c:
533    case K2.DataApi{}:
534      True{}
535    case _:
536      False{}
537
538# The system's own data APIs. "Exactly one" means one store.
539def data_apis(cs: List<&2, Ct.Container>) -> Nat:
540  match cs:
541    case []:
542      0n
543    case c <> rest:
544      match c:
545        case Ct.Container{_, _, _, cat, _, _, _, _, _, _, _, _, _, _, _}:
546          Nat.add(one_if(is_data_api(cat)), data_apis(rest))
547
548def own_data_apis(+a: A.Architecture) -> Nat:
549  data_apis(containers(a))
550
551# A data API may also host the app only while it is private.
552def shared_ok(c: Ct.Container) -> Bool:
553  match c:
554    case Ct.Container{_, _, _, cat, _, +e, +caps, _, _, _, _, _, _, _, _}:
555      Bool.not(is_data_api(cat) && has_cap(K2.Hosting{}, caps)) || is_tailnet(e) || is_local(e)
556
557def all_shared_ok(cs: List<&2, Ct.Container>) -> Bool:
558  match cs:
559    case []:
560      True{}
561    case c <> rest:
562      shared_ok(c) && all_shared_ok(rest)
563
564def shared_hosting_private(+a: A.Architecture) -> Bool:
565  all_shared_ok(containers(a))
566
567# ---- observed: everything we build reports to an observability provider ----
568def reports(+k: String, es: List<&2, E.Edge>, +cs: List<&2, Ct.Container>, +obs: List<&2, String>) -> Bool:
569  match es:
570    case []:
571      False{}
572    case e <> rest:
573      match e:
574        case E.Edge{x, y, _, _}:
575          (String.eq(home(x, cs), k) && CO.has(obs, y)) || reports(k, rest, cs, obs)
576
577def built_observed(cs: List<&2, Ct.Container>, +a: A.Architecture) -> Bool:
578  match cs:
579    case []:
580      True{}
581    case c <> rest:
582      match c:
583        case Ct.Container{+k, _, _, _, o, _, _, +src, _, _, _, _, _, _, _}:
584          (Bool.not(ours(o) && Bool.not(String.is_empty(src))) || reports(k, edges(a), containers(a), providers(K2.Observability{}, a))) &&
585            built_observed(rest, a)
586
587def observed(+a: A.Architecture) -> Bool:
588  built_observed(containers(a), a)
589
590# ---- the environment's inventory and agreement ----
591def inventory(a: A.Architecture) -> I.Inventory:
592  match a:
593    case A.Architecture{_, env}:
594      match env:
595        case En.Environment{_, _, _, inv, _}:
596          inv
597
598def agreement(a: A.Architecture) -> G.Agreement:
599  match a:
600    case A.Architecture{_, env}:
601      match env:
602        case En.Environment{_, _, _, _, ag}:
603          ag
604
605# ---- every server and device is the owner's, or on its vendor's cloud ----
606def host_ok(h: H.Host, +name: String, +inv: I.Inventory) -> Bool:
607  match h:
608    case H.Owned{k}:
609      InvO.owns(inv, k, name)
610    case H.Cloud{p}:
611      InvO.cloud_ok(inv, p)
612    case H.Other{}:
613      False{}
614
615def nodes_owned(ns: List<&2, D.Node>, +inv: I.Inventory) -> Bool:
616  match ns:
617    case []:
618      True{}
619    case n <> rest:
620      match n:
621        case D.Node{_, +name, _, _, h, _, kids, _}:
622          host_ok(h, name, inv) && nodes_owned(kids, inv) && nodes_owned(rest, inv)
623
624def owned_nodes(+a: A.Architecture) -> Bool:
625  nodes_owned(nodes(a), inventory(a))
626
627# ---- every nix-darwin node: its serve ports are distinct, its services and serves run what it hosts, a
628# container on it is on the tailnet exactly when it is served, and its listeners belong to something ----
629def firewall_typed(nd: ND.NixDarwin) -> Bool:
630  match nd:
631    case ND.NixDarwin{_, _, _, _, _, st, _, _, _, _, _, _, _, _}:
632      StO.firewall_typed(st)
633
634def node_keys(ns: List<&2, D.Node>) -> List<&2, String>:
635  match ns:
636    case []:
637      []
638    case n <> rest:
639      match n:
640        case D.Node{k, _, _, _, _, _, kids, _}:
641          k <> List.append(&2, String, node_keys(kids), node_keys(rest))
642
643def darwin_ok(p: D.Platform, +ins: List<&2, String>, +a: A.Architecture) -> Bool:
644  match p:
645    case D.Darwin{+nd}:
646      firewall_typed(nd) && NDO.serve_ports_distinct(nd) && NDO.services_on(nd, ins) && NDO.exposure_honest(nd, containers(a), ins) &&
647        NDO.listeners_belong(nd, List.append(&2, String, container_keys(containers(a)), node_keys(nodes(a))))
648    case _:
649      True{}
650
651def darwin_nodes_ok(ns: List<&2, D.Node>, +a: A.Architecture) -> Bool:
652  match ns:
653    case []:
654      True{}
655    case n <> rest:
656      match n:
657        case D.Node{_, _, _, _, _, p, kids, ins}:
658          darwin_ok(p, ins, a) && darwin_nodes_ok(kids, a) && darwin_nodes_ok(rest, a)
659
660def nix_darwin_ok(+a: A.Architecture) -> Bool:
661  darwin_nodes_ok(nodes(a), a)