Dark Mode
MACM Validity Checks
This is a list of syntactic and semantic checks to validate the correctness of a MACM model.
Check Table
| Check ID | Name | Description | Query | Activated |
|---|---|---|---|---|
1 |
Asset Type Validity | Per ogni nodo: - Il valore della proprietà `asset type` deve appartenere all’insieme T dei tipi ammessi. - La coppia `(PrimaryLabel, SecondaryLabel)` associata a quel valore di asset_type deve corrispondere ai valori (primary_label, secondary_label) del nodo. | CALL apoc.trigger.add( 'check_asset_type_labels', ' UNWIND coalesce($createdNodes, []) AS n WITH n WHERE n.type IS NOT NULL WITH n, split(n.type, ".")[0] AS plFromType, labels(n) AS lbls CALL apoc.util.validate( NOT (plFromType IN lbls), apoc.text.format( "/* %s type=%s expects primary label %s but labels are [%s] */", [coalesce(n.name,"<no name>"), n.type, plFromType, apoc.text.join(lbls, ",")] ), [] ) WITH n, plFromType, lbls, ["Party","CSP","HW","Network","Service","Virtual","SystemLayer","Data"] AS macro WITH n, plFromType, [l IN lbls WHERE l <> plFromType AND NOT l IN macro] AS rest WITH n, plFromType, rest, [ {pl:"Party",sl:"Human",types:["Party.Human"]}, {pl:"Party",sl:"LegalEntity",types:["Party.LegalEntity"]}, {pl:"Party",sl:"Group",types:["Party.Group"]}, {pl:"CSP",sl:null,types:["CSP"]}, {pl:"HW",sl:"MEC",types:["HW.MEC"]}, {pl:"HW",sl:"HW.GCS",types:["HW.GCS"]}, {pl:"HW",sl:"UE",types:["HW.UE"]}, {pl:"HW",sl:"Chassis",types:["HW.Chassis"]}, {pl:"HW",sl:"Raspberry",types:["HW.Raspberry"]}, {pl:"HW",sl:"Router",types:["HW.Router"]}, {pl:"HW",sl:"IoT",types:["HW.IoT.Device","HW.IoT.Gateway"]}, {pl:"HW",sl:"Device",types:["HW.Device","HW.HDI"]}, {pl:"HW",sl:"Server",types:["HW.Server"]}, {pl:"HW",sl:"Microcontroller",types:["HW.Microcontroller"]}, {pl:"HW",sl:"SOC",types:["HW.SOC"]}, {pl:"HW",sl:"PC",types:["HW.PC","HW.PC.LoginNode","HW.PC.DataStorageDisk","HW.PC.SchedulerNode","HW.PC.ComputeNode"]}, {pl:"Network",sl:null,types:["Network"]}, {pl:"Network",sl:"WAN",types:["Network.WAN"]}, {pl:"Network",sl:"LAN",types:["Network.LAN","Network.Wired","Network.WiFi","Network.Virtual"]}, {pl:"Network",sl:"PAN",types:["Network.PAN"]}, {pl:"Network",sl:"5G",types:["Network.RAN","Network.Core"]}, {pl:"SystemLayer",sl:"OS",types:["SystemLayer.OS"]}, {pl:"SystemLayer",sl:"Firmware",types:["SystemLayer.Firmware"]}, {pl:"SystemLayer",sl:"HyperVisor",types:["SystemLayer.HyperVisor"]}, {pl:"SystemLayer",sl:"ContainerRuntime",types:["SystemLayer.ContainerRuntime"]}, {pl:"Virtual",sl:"VM",types:["Virtual.VM"]}, {pl:"Virtual",sl:"Container",types:["Virtual.Container"]}, {pl:"Service",sl:"5G",types:["Service.5G.RAN","Service.5G.AMF","Service.5G.AUSF","Service.5G.NEF","Service.5G.NRF","Service.5G.NSSF","Service.5G.NWDAF","Service.5G.PCF","Service.5G.UDM","Service.5G.UPF"]}, {pl:"Service",sl:"App",types:["Service.App","Service.Browser","Service.MQTTClient","Service.QueueClient"]}, {pl:"Service",sl:null,types:["Service"]}, {pl:"Service",sl:"Server",types:["Service.JobScheduler","Service.SSH","Service.Web","Service.API","Service.DB","Service.NoSQLDB","Service.IDProvider","Service.MQTTBroker","Service.RPCBind","Service.CA","Service.QueueServer"]} ] AS mapping WITH n, plFromType, rest, [m IN mapping WHERE m.pl = plFromType AND n.type IN m.types | m.sl] AS expectedSLsRaw WITH n, plFromType, rest, [x IN expectedSLsRaw WHERE x IS NOT NULL] AS expectedSome, any(x IN expectedSLsRaw WHERE x IS NULL) AS noneAllowed, size(expectedSLsRaw) AS hasAnyMapping CALL apoc.util.validate( hasAnyMapping = 0, apoc.text.format( "/* %s type=%s PL=%s is not covered by mapping */", [coalesce(n.name,"<no name>"), n.type, plFromType] ), [] ) WITH n, plFromType, rest, expectedSome, noneAllowed, (CASE WHEN noneAllowed THEN "[]" ELSE "[" + apoc.text.join(expectedSome, ",") + "]" END) AS expectedStr, apoc.text.join(rest, ",") AS restStr CALL apoc.util.validate( (noneAllowed AND size(rest) <> 0) OR (NOT noneAllowed AND (size(rest) <> 1 OR NOT rest[0] IN expectedSome)), apoc.text.format( "/* %s type=%s PL=%s expected SL(s)=%s but secondary labels are [%s] */", [coalesce(n.name,"<no name>"), n.type, plFromType, expectedStr, restStr] ), [] ) RETURN 0; ', {phase:'before'} ); | Yes |
2 |
Single Hosting/Providing per Asset | Each asset must be hosted or provided by **at most** one other node. No service can have multiple hosts or provider. | CALL apoc.trigger.add( 'check_single_host_provide_per_asset', ' MATCH (hoster)-[r]->(s) WHERE type(r) IN ["hosts","provides"] WITH s, COUNT(DISTINCT hoster) AS numHosts WHERE numHosts > 1 WITH collect(s.name) AS violations CALL apoc.util.validate( size(violations) > 0, "/*Rule 2 violation: the following service has more than a host/provider:" + apoc.text.join (violations, ",") + "*/", [] ) RETURN true ', {phase:'before'} ); | Yes |
3 |
Mandatory host/provide per service | Ogni nodo di tipo Service deve essere collegato da esattamente un altro nodo tramite una relazione :hosts o :provides. In altre parole - Nessun Service può essere “orfano” (0 host/provide); - Nessun Service può avere due o più host/provide contemporaneamente. | CALL apoc.trigger.add( 'check_mandatory_host_provide_for_service', ' MATCH (s:Service) OPTIONAL MATCH (hoster)-[r]->(s) WHERE type(r) IN ["hosts","provides"] WITH s, COUNT(DISTINCT hoster) AS numHosts WHERE numHosts < 1 WITH collect(s.name) AS violations CALL apoc.util.validate( size(violations) > 0, "/*Rule 3 violation: the following service do not have a host/provider:" + apoc.text.join (violations, ",") + "*/", [] ) RETURN true ', {phase:'before'} ); | Yes |
4 |
Alternate path for uses | Ogni relazione uses tra due nodi deve avere almeno un percorso alternativo che li colleghi senza usare altre uses o interacts. | CALL apoc.trigger.add( 'check_alternate_path_for_uses', ' MATCH (a)-[r:uses]->(b) WHERE NOT EXISTS { MATCH p=(a)-[*]-(b) WHERE ALL(rel IN relationships(p) WHERE type(rel) <> "uses") } WITH collect(a.name + "->" + b.name) AS violazioni CALL apoc.util.validate( size(violazioni) > 0, "/*Rule 4 violation: uses relationships without alternative path: " + apoc.text.join(violazioni, ", ") + "*/", [] ) RETURN true ', {phase: 'before'} ); | Yes |
5 |
SystemLayer hosting SystemLayer node validity | Solo un nodo SoftLayer di tipo sistema operativo può ospitare nodi SoftLayer che rappresentano ambienti di containerizzazione o macchine virtuali; qualsiasi altra combinazione non è permessa. | CALL apoc.trigger.add( 'check_SystemLayer_hosts_SystemLayer', ' MATCH (src)-[r:hosts]->(dst) WITH head(labels(src)) AS primarySrc, head(labels(dst)) AS primaryDst, src, dst WHERE primarySrc = "SystemLayer" AND primaryDst = "SystemLayer" AND NOT ( src.type = "SystemLayer.OS" AND dst.type IN ["SystemLayer.ContainerRuntime", "SystemLayer.HyperVisor"] ) WITH collect(src.name + " hosts " + dst.name) AS violazioni CALL apoc.util.validate( size(violazioni) > 0, "/*Rule 5 violation: hosting relationships between SystemLayer nodes not valid: " + apoc.text.join(violazioni, ", ")+"*/", [] ) RETURN true ', {phase: 'before'} ); | Yes |
6 |
SystemLayer hosting Virtual node validity | Quando un nodo SoftLayer ospita un nodo Virtual, il nodo SoftLayer deve essere di tipo ContainerRuntime o HyperVisor. Se il nodo SoftLayer è di tipo ContainerRuntime, allora il nodo `Virtual` ospitato deve essere un Virtual.Container (cioè un container virtuale). Se il nodo SoftLayer è di tipo HyperVisor, allora il nodo Virtual ospitato deve essere una Virtual.VM (una macchina virtuale). | CALL apoc.trigger.add( 'check_SystemLayer_hosts_virtual', ' MATCH (src)-[r:hosts]->(dst) WITH head(labels(src)) AS primarySrc, head(labels(dst)) AS primaryDst, src, dst WHERE primarySrc = "SystemLayer" AND primaryDst = "Virtual" AND NOT ( (src.type = "SystemLayer.ContainerRuntime" AND dst.type = "Virtual.Container") OR (src.type = "SystemLayer.HyperVisor" AND dst.type = "Virtual.VM") ) WITH collect(src.name + " hosts " + dst.name) AS violazioni CALL apoc.util.validate( size(violazioni) > 0, "/*Rule 6 violation: hosting relationships between SystemLayer and Virtual nodes not valid: " + apoc.text.join(violazioni, ", ")+"*/", [] ) RETURN true ', {phase: 'before'} ); | Yes |
7 |
SystemLayer hosting Service node validity | I nodi hardware possono hostare solo servizi di tipo SoftLayer.Firmware o SoftLayer.OS. | CALL apoc.trigger.add( 'check_SystemLayer_hosts_services', ' MATCH (src)-[r:hosts]->(dst) WHERE head(labels(src)) = "SystemLayer" AND head(labels(dst)) = "Service" AND NOT src.type IN ["SystemLayer.Firmware", "SystemLayer.OS"] WITH collect(src.name + " (type: " + src.type + ")" + " hosts " + dst.name) AS violazioni CALL apoc.util.validate( size(violazioni) > 0, "/*Rule 7 violation: hosting relationships between SystemLayer and Service nodes not valid: " + apoc.text.join(violazioni, ", ") + "*/", [] ) RETURN true ', {phase: 'before'} ); | Yes |
8 |
Virtual hosting SystemLayer node validity | Quando un nodo Virtual hosts un nodo SoftLayer, il nodo SoftLayer ospitato deve avere asset type uguale a SoftLayer.OS oppure SoftLayer.Firmware,altrimenti viene generato un errore. | CALL apoc.trigger.add( 'check_virtual_hosts_SystemLayer', ' MATCH (src)-[r:hosts]->(dst) WITH head(labels(src)) AS primarySrc, head(labels(dst)) AS primaryDst, src, dst WHERE primarySrc = "Virtual" AND primaryDst = "SystemLayer" AND NOT (dst.type IN ["SystemLayer.OS", "SystemLayer.Firmware"]) WITH collect(src.name + " hosts " + dst.name) AS violazioni CALL apoc.util.validate( size(violazioni) > 0, "/*Rule 8 violation: hosting relationships between Virtual and SystemLayer nodes not valid: " + apoc.text.join(violazioni, ", ") + "*/", [] ) RETURN true ', {phase: 'before'} ); | Yes |
9 |
Hardware hosting SystemLayer node validity | Se un nodo HW (hardware) hosts un nodo SoftLayer, il nodo SoftLayer ospitato non può avere asset type SoftLayer.ContainerRuntime,altrimenti viene generato un errore. | CALL apoc.trigger.add( 'check_hw_hosts_SystemLayer', ' MATCH (src)-[r:hosts]->(dst) WITH head(labels(src)) AS primarySrc, head(labels(dst)) AS primaryDst, src, dst WHERE primarySrc = "HW" AND primaryDst = "SystemLayer" AND dst.type = "SystemLayer.ContainerRuntime" WITH collect(src.name + " hosts " + dst.name) AS violazioni CALL apoc.util.validate( size(violazioni) > 0, "/*Rule 9 violation: hosting relationships between HW and SystemLayer nodes not valid (ContainerRuntime not allowed): " + apoc.text.join(violazioni, ", ") + "*/", [] ) RETURN true ', {phase: 'before'} ); | Yes |
10 |
Graph Connectivity | Il grafo deve essere connesso. | CALL apoc.trigger.add( 'check_graph_connectivity_global', ' // scegliamo un nodo a caso come sorgente MATCH (start) WITH start LIMIT 1 // percorsi di lunghezza 0.. → lo start è incluso nei reachable MATCH (start)-[*0..]-(reachable) WITH start, collect(DISTINCT id(reachable)) AS reachIds // tutti i nodi del grafo MATCH (n) WITH start, reachIds, collect(id(n)) AS allIds WITH start, reachIds, allIds, [x IN allIds WHERE NOT x IN reachIds] AS missingIds // nodi non raggiungibili OPTIONAL MATCH (m) WHERE id(m) IN missingIds WITH start, [m IN collect(m) | coalesce(m.name, head(labels(m)), toString(id(m)))] AS notReachable CALL apoc.util.validate( size(notReachable) > 0, "/*Connectivity violation: the graph is not connected. Start node: " + coalesce(start.name, head(labels(start)), toString(id(start))) + ". Unreachable nodes: " + apoc.text.join(notReachable, ", ") + "*/", [] ) RETURN true ', {phase: 'before'} ); | Yes |
11 |
Single host/provide per SystemLayer | Ogni nodo di tipo SystemLayer deve essere collegato da esattamente un altro nodo tramite una relazione :hosts o :provides. In altre parole - Nessun SystemLayer può essere “orfano” (0 host/provide); - Nessun SystemLayer può avere due o più host/provide contemporaneamente. | CALL apoc.trigger.add( 'check_mandatory_host_provide_for_systemlayer', ' MATCH (s:SystemLayer) OPTIONAL MATCH (hoster)-[r]->(s) WHERE type(r) IN ["hosts","provides"] WITH s, COUNT(DISTINCT hoster) AS numHosts WHERE numHosts < 1 WITH collect(s.name) AS violations CALL apoc.util.validate( size(violations) > 0, "/*Rule 12 violation: the following SystemLayer do not have any host/provider:" + apoc.text.join (violations, ",") + "*/", [] ) RETURN true ', {phase:'before'} ); | Yes |
12 |
Single host/provide per Virtual | Each `Virtual` node must be hosted or provided by **exactly one** other node. No Virtual can be orphaned or have multiple hosts. | CALL apoc.trigger.add( 'check_mandatory_host_provide_for_virtual', ' MATCH (s:Virtual) OPTIONAL MATCH (hoster)-[r]->(s) WHERE type(r) IN ["hosts","provides"] WITH s, COUNT(DISTINCT hoster) AS numHosts WHERE numHosts < 1 WITH collect(s.name) AS violations CALL apoc.util.validate( size(violations) > 0, "/*Rule 13 violation: the following Virtual do not have any host/provider:" + apoc.text.join (violations, ",") + "*/", [] ) RETURN true ', {phase:'before'} ); | Yes |
13 |
No cycles allowed for hosts relationship | Ensures acyclicity of the :hosts relation, preventing recursive or circular hosting dependencies among system assets. | CALL apoc.trigger.add( 'check_hosts_acyclic_with_cycles', ' MATCH p = (n)-[:hosts*1..]->(n) WITH DISTINCT p, [x IN nodes(p) | toString(coalesce(x.name, id(x)))] AS lst WITH CASE WHEN size(lst) > 1 AND lst[0] = lst[size(lst)-1] THEN lst[0..size(lst)-1] ELSE lst END AS core WHERE size(core) > 0 WITH [i IN range(0, size(core)-1) | apoc.text.join(core[i..] + core[..i] + [core[i]], " -> ") ] AS rots WITH apoc.coll.min(rots) AS cycleCanon WITH apoc.coll.toSet(collect(cycleCanon)) AS cycles CALL apoc.util.validate( size(cycles) > 0, "/*Constraint violation: cycles detected in :hosts hierarchy:\\n" + apoc.text.join(cycles[0..20], "\\n") + "*/", [] ) RETURN true ', {phase:'before'} ); | Yes |
14 |
Relationship validity patterns | $\delta(SourcePrimaryLabel,TargetPrimaryLabel)=RelationshipType$ | CALL apoc.trigger.add( 'check_allowed_relationships', ' // relazioni ammesse (macro-primarie) WITH [ ["Party", "interacts", "Service"], ["Party", "interacts", "HW"], ["Party", "interacts", "Network"], ["Party", "interacts", "Party"], ["Party", "interacts", "Virtual"], ["Party", "interacts", "SystemLayer"], ["Party", "interacts", "CSP"], ["Service", "uses", "Service"], ["Service", "uses", "Virtual"], ["Service", "hosts", "Service"], ["Virtual", "hosts", "SystemLayer"], ["SystemLayer", "hosts", "SystemLayer"], ["SystemLayer", "hosts", "Virtual"], ["SystemLayer", "hosts", "Service"], ["SystemLayer", "hosts", "Network"], ["SystemLayer", "uses", "HW"], ["HW", "hosts", "HW"], ["HW", "hosts", "SystemLayer"], ["CSP", "provides", "Service"], ["CSP", "provides", "Network"], ["CSP", "provides", "HW"], ["CSP", "provides", "Virtual"], ["CSP", "provides", "SystemLayer"], ["Network", "connects", "Network"], ["Network", "connects", "Virtual"], ["Network", "connects", "HW"], ["Network", "connects", "CSP"] ] AS allowed, coalesce($createdRelationships, []) AS rels, // solo le relazioni toccate nel tx ["Party","CSP","HW","Network","Service","Virtual","SystemLayer","Data"] AS macro UNWIND rels AS r WITH allowed, macro, startNode(r) AS src, type(r) AS relType, endNode(r) AS dst // primary label robusta: prima macro presente nelle label del nodo WITH allowed, relType, [m IN macro WHERE m IN labels(src)][0] AS primarySrc, [m IN macro WHERE m IN labels(dst)][0] AS primaryDst WITH allowed, primarySrc, relType, primaryDst WHERE NOT any(rule IN allowed WHERE rule[0]=primarySrc AND rule[1]=relType AND rule[2]=primaryDst) WITH collect(coalesce(primarySrc,"<no-PL>") + "-" + relType + "->" + coalesce(primaryDst,"<no-PL>")) AS violations CALL apoc.util.validate( size(violations) > 0, "/*Constraint violated: Unauthorized relationship(s): " + apoc.text.join(violations, ", ")+"*/", [] ) RETURN true; ', {phase:'before'} ); | Yes |
| Check ID | Name | Description | Query | Activated |