From a9d14326eec3b35ef43c569682ddd1e5256aa0a1 Mon Sep 17 00:00:00 2001 From: Rich Turner <7072278+richturner@users.noreply.github.com> Date: Wed, 17 Jun 2026 13:57:52 +0100 Subject: [PATCH 1/7] Add asset validation docs page --- docs/architecture/asset-validation | 132 +++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 docs/architecture/asset-validation diff --git a/docs/architecture/asset-validation b/docs/architecture/asset-validation new file mode 100644 index 00000000..9d91d45a --- /dev/null +++ b/docs/architecture/asset-validation @@ -0,0 +1,132 @@ +# Asset Validation + +This document summarises how asset validation is perforomed and how it relates to the asset type model. + +## AssetTypeInfo Model Structure + +``` +┌─────────────────────────────────────────────────────────-──┐ +│ AssetTypeInfo │ +│ (AssetTypeInfo.java) │ +│ │ +│ assetDescriptor : AssetDescriptor │ +│ attributeDescriptors : Map>│ +│ metaItemDescriptors : MetaItemDescriptor[] │ +│ valueDescriptors : ValueDescriptor[] │ +└──────┬──────────────────────────────────────────────────┬-─┘ + │ │ + ▼ ▼ +┌──────────────────────┐ ┌─────────────────────────────-─┐ +│ AssetDescriptor │ │ AttributeDescriptor │ +│ │ │ (extends │ +│ name : String │ │ AbstractNameValueDescriptor │ +│ type : Class │ │ Holder) │ +│ icon : String │ │ │ +│ color: String │ │ ── Inherited ── │ +│ (agent subtype: │ │ name : String │ +│ AgentDescriptor) │ │ type : ValueDescriptor│ +└──────────────────────┘ │ constraints : ValueConstraint│ + │ format : ValueFormat │ + │ units : String[] │ + │ │ + │ ── Own ── │ + │ meta : MetaMap │ + │ optional : Boolean │ + └────────────┬─────────────────-┘ + │ + ┌───────────────────────┼─────────────-────┐ + ▼ ▼ ▼ + ┌──────────────────┐ ┌────────────────────┐ ┌───────────────-┐ + │ ValueDescriptor │ │ MetaMap │ │ ValueConstraint│ + │ │ │ (attribute-level │ │ (abstract) │ + │ name : String │ │ meta items, │ │ │ + │ type : Class│ │ keyed by name) │ │ evaluate() │ + │ constraints[] │ │ │ │ message │ + │ arrayDimensions │ │ ── Can hold ── │ │ getParams() │ + │ jsonType │ │ MetaItemType │ └──────┬──────-──┘ + └──────────────────┘ │ .CONSTRAINTS ──► │ │ + │ ValueConstraint[] │ ┌──────┴──────────────────────────────────────-──┐ + └────────────────────┘ │ Concrete subtypes (discriminated by "type":) │ + │ Size, Min, Max, Pattern, AllowedValues, │ + │ Past, PastOrPresent, Future, FutureOrPresent, │ + │ NotEmpty, NotBlank, NotNull │ + └────────────────────────────────────────────────┘ +``` +--- + +## Constraint Validation Logic in ValueUtil.validateValue() + +There are four independent sources of constraints, all evaluated cumulatively. A value is valid only if it passes every constraint from every source. + +``` +ValueUtil.validateValue( + attributeDescriptor, // static descriptor (from AssetTypeInfo) + valueDescriptor, // the value's type descriptor + metaHolder, // the live attribute instance (has its own MetaMap) + now, context, value +) + │ + ├─── [1] ValueDescriptor.constraints[] ← constraints on the VALUE TYPE itself + │ e.g. ValueType.NUMBER might have Min(0) + │ validateConstraints(arrayDimensions, constraints, ...) + │ + ├─── [2] AttributeDescriptor.constraints[] ← constraints on the ATTRIBUTE DESCRIPTOR + │ e.g. a specific attribute defined with .withConstraints(new Max(100)) + │ validateConstraints(arrayDimensions, constraints, ...) + │ + ├─── [3] AttributeDescriptor.meta ← CONSTRAINTS meta on the DESCRIPTOR + │ attributeDescriptor.getMeta() + │ .get(MetaItemType.CONSTRAINTS) // MetaItemDescriptor + │ .flatMap(ValueHolder::getValue) // Optional + │ Each constraint → validateValueConstraint(...) + │ + └─── [4] metaHolder.getMeta() ← CONSTRAINTS meta on the LIVE ATTRIBUTE + (the attribute instance itself, not the descriptor) + .get(MetaItemType.CONSTRAINTS) + Each constraint → validateValueConstraint(...) +``` + +## Array value recursion (validateConstraints) + +```java +// ValueUtil.java:1043 +private static boolean validateConstraints(Integer dimensions, ValueConstraint[] constraints, ..., Object value) { + if (dimensions == null || dimensions == 0 || value == null) { + // Leaf: evaluate each constraint directly against the value + return Arrays.stream(constraints).map(c -> validateValueConstraint(..., c, value)) + .anyMatch(valid -> !valid); + } else { + // Array: recurse into each element, decrementing dimension count + return Arrays.stream((Object[]) value) + .anyMatch(v -> validateConstraints(dimensions - 1, constraints, ..., v)); + } +} +``` + +When valueDescriptor.arrayDimensions > 0, constraints are applied to each element, not the array itself (dimension is decremented on each recursion). Paths for error reporting are not updated during recursion, so violations always point to the attribute root. + +## `validateValueConstraint` (the leaf evaluator) + +'''java +// ValueUtil.java:1051 +public static boolean validateValueConstraint(..., ValueConstraint valueConstraint, Object value) { + if (!valueConstraint.evaluate(value, now)) { + // 1. Inject message parameters (min/max/pattern/etc.) into Hibernate context + // 2. Build violation with constraint's message template key + // 3. Apply the path provider to set the property path + return false; // invalid + } + return true; // valid +} +''' + +## Constraint source summary + +| Source | Where defined | Applied via | +|--------|--------------|-------------| +| ValueDescriptor.constraints[] | On the value type (e.g. ValueType.NUMBER) | validateConstraints() with array recursion | +| AttributeDescriptor.constraints[] | On the static attribute definition | validateConstraints() with array recursion | +| AttributeDescriptor.meta[CONSTRAINTS] | In the descriptor's MetaMap, key "constraints" | Direct per-constraint loop | +| metaHolder.meta[CONSTRAINTS] | On the live Attribute instance's MetaMap | Direct per-constraint loop | + +The key distinction between sources 3 and 4 is that source 3 comes from the static descriptor (set by the developer defining the asset model), while source 4 comes from the live attribute (set at runtime, e.g. by a user configuring constraints via the UI or API). Both are checked with MetaItemType.CONSTRAINTS — the same MetaItemDescriptor. From ecb2fd6337f896e411bb88f0ba07934201092616 Mon Sep 17 00:00:00 2001 From: Rich Turner <7072278+richturner@users.noreply.github.com> Date: Wed, 17 Jun 2026 14:36:55 +0100 Subject: [PATCH 2/7] Cleanup indexing --- docs/architecture/apps-and-consoles.md | 2 +- docs/architecture/asset-location-tracking.md | 2 +- docs/architecture/{asset-validation => asset-validation.md} | 4 ++++ docs/architecture/esp32-device.md | 2 +- docs/architecture/manager-endpoints-and-file-paths.md | 2 +- docs/architecture/security.md | 2 +- 6 files changed, 9 insertions(+), 5 deletions(-) rename docs/architecture/{asset-validation => asset-validation.md} (99%) diff --git a/docs/architecture/apps-and-consoles.md b/docs/architecture/apps-and-consoles.md index baccea84..4da14432 100644 --- a/docs/architecture/apps-and-consoles.md +++ b/docs/architecture/apps-and-consoles.md @@ -1,5 +1,5 @@ --- -sidebar_position: 3 +sidebar_position: 6 --- # Apps and consoles diff --git a/docs/architecture/asset-location-tracking.md b/docs/architecture/asset-location-tracking.md index d826144d..0cfe7d0d 100644 --- a/docs/architecture/asset-location-tracking.md +++ b/docs/architecture/asset-location-tracking.md @@ -1,5 +1,5 @@ --- -sidebar_position: 4 +sidebar_position: 5 --- # Asset location tracking diff --git a/docs/architecture/asset-validation b/docs/architecture/asset-validation.md similarity index 99% rename from docs/architecture/asset-validation rename to docs/architecture/asset-validation.md index 9d91d45a..9a400a91 100644 --- a/docs/architecture/asset-validation +++ b/docs/architecture/asset-validation.md @@ -1,3 +1,7 @@ +--- +sidebar_position: 4 +--- + # Asset Validation This document summarises how asset validation is perforomed and how it relates to the asset type model. diff --git a/docs/architecture/esp32-device.md b/docs/architecture/esp32-device.md index 41eccdc2..3204480f 100644 --- a/docs/architecture/esp32-device.md +++ b/docs/architecture/esp32-device.md @@ -1,5 +1,5 @@ --- -sidebar_position: 5 +sidebar_position: 7 --- # ESP32 devices diff --git a/docs/architecture/manager-endpoints-and-file-paths.md b/docs/architecture/manager-endpoints-and-file-paths.md index 3e7dafcd..5e0515bc 100644 --- a/docs/architecture/manager-endpoints-and-file-paths.md +++ b/docs/architecture/manager-endpoints-and-file-paths.md @@ -1,5 +1,5 @@ --- -sidebar_position: 1 +sidebar_position: 2 --- # Manager endpoints and file paths diff --git a/docs/architecture/security.md b/docs/architecture/security.md index 08e90954..91f2110c 100644 --- a/docs/architecture/security.md +++ b/docs/architecture/security.md @@ -1,5 +1,5 @@ --- -sidebar_position: 2 +sidebar_position: 3 --- # Security From 816a106a8c41be9c9de39ade3a92991abc562d40 Mon Sep 17 00:00:00 2001 From: Rich Turner <7072278+richturner@users.noreply.github.com> Date: Wed, 17 Jun 2026 16:23:32 +0100 Subject: [PATCH 3/7] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- docs/architecture/asset-validation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/architecture/asset-validation.md b/docs/architecture/asset-validation.md index 9a400a91..90d0fd33 100644 --- a/docs/architecture/asset-validation.md +++ b/docs/architecture/asset-validation.md @@ -4,7 +4,7 @@ sidebar_position: 4 # Asset Validation -This document summarises how asset validation is perforomed and how it relates to the asset type model. +This document summarises how asset validation is performed and how it relates to the asset type model. ## AssetTypeInfo Model Structure From b86cd2eec28b8405c4ae8acedd7688a4c380c023 Mon Sep 17 00:00:00 2001 From: Rich Turner <7072278+richturner@users.noreply.github.com> Date: Wed, 17 Jun 2026 16:30:05 +0100 Subject: [PATCH 4/7] Change diagram to mermaid --- docs/architecture/asset-validation.md | 113 +++++++++++++++----------- 1 file changed, 66 insertions(+), 47 deletions(-) diff --git a/docs/architecture/asset-validation.md b/docs/architecture/asset-validation.md index 90d0fd33..0f8c33c8 100644 --- a/docs/architecture/asset-validation.md +++ b/docs/architecture/asset-validation.md @@ -8,53 +8,72 @@ This document summarises how asset validation is performed and how it relates to ## AssetTypeInfo Model Structure -``` -┌─────────────────────────────────────────────────────────-──┐ -│ AssetTypeInfo │ -│ (AssetTypeInfo.java) │ -│ │ -│ assetDescriptor : AssetDescriptor │ -│ attributeDescriptors : Map>│ -│ metaItemDescriptors : MetaItemDescriptor[] │ -│ valueDescriptors : ValueDescriptor[] │ -└──────┬──────────────────────────────────────────────────┬-─┘ - │ │ - ▼ ▼ -┌──────────────────────┐ ┌─────────────────────────────-─┐ -│ AssetDescriptor │ │ AttributeDescriptor │ -│ │ │ (extends │ -│ name : String │ │ AbstractNameValueDescriptor │ -│ type : Class │ │ Holder) │ -│ icon : String │ │ │ -│ color: String │ │ ── Inherited ── │ -│ (agent subtype: │ │ name : String │ -│ AgentDescriptor) │ │ type : ValueDescriptor│ -└──────────────────────┘ │ constraints : ValueConstraint│ - │ format : ValueFormat │ - │ units : String[] │ - │ │ - │ ── Own ── │ - │ meta : MetaMap │ - │ optional : Boolean │ - └────────────┬─────────────────-┘ - │ - ┌───────────────────────┼─────────────-────┐ - ▼ ▼ ▼ - ┌──────────────────┐ ┌────────────────────┐ ┌───────────────-┐ - │ ValueDescriptor │ │ MetaMap │ │ ValueConstraint│ - │ │ │ (attribute-level │ │ (abstract) │ - │ name : String │ │ meta items, │ │ │ - │ type : Class│ │ keyed by name) │ │ evaluate() │ - │ constraints[] │ │ │ │ message │ - │ arrayDimensions │ │ ── Can hold ── │ │ getParams() │ - │ jsonType │ │ MetaItemType │ └──────┬──────-──┘ - └──────────────────┘ │ .CONSTRAINTS ──► │ │ - │ ValueConstraint[] │ ┌──────┴──────────────────────────────────────-──┐ - └────────────────────┘ │ Concrete subtypes (discriminated by "type":) │ - │ Size, Min, Max, Pattern, AllowedValues, │ - │ Past, PastOrPresent, Future, FutureOrPresent, │ - │ NotEmpty, NotBlank, NotNull │ - └────────────────────────────────────────────────┘ +```mermaid +classDiagram + direction TB + + class AssetTypeInfo { + +AssetDescriptor~?~ assetDescriptor + +Map attributeDescriptors + +MetaItemDescriptor~?~[] metaItemDescriptors + +ValueDescriptor~?~[] valueDescriptors + } + note for AssetTypeInfo "Source: AssetTypeInfo.java" + + class AssetDescriptor~T~ { + +String name + +Class~T~ type + +String icon + +String color + } + note for AssetDescriptor "Agent subtype: AgentDescriptor" + + class AttributeDescriptor~T~ { + +String name [Inherited] + +ValueDescriptor type [Inherited] + +ValueConstraint constraints [Inherited] + +ValueFormat format [Inherited] + +String[] units [Inherited] + +MetaMap meta [Own] + +Boolean optional [Own] + } + note for AttributeDescriptor "Extends AbstractNameValueDescriptor, Holder~T~" + + class ValueDescriptor { + +String name + +Class~T~ type + +constraints[] + +arrayDimensions + +jsonType + } + + class MetaMap { + +ValueConstraint[] CONSTRAINTS + } + note for MetaMap "Attribute-level meta items\nKeyed by name" + + class ValueConstraint { + <> + +evaluate() + +message + +getParams() + } + + class ConcreteSubtypes { + Size, Min, Max, Pattern, AllowedValues + Past, PastOrPresent, Future, FutureOrPresent + NotEmpty, NotBlank, NotNull + } + note for ConcreteSubtypes "Discriminated by 'type' field" + + %% Relationships & Structural Dependencies + AssetTypeInfo --> AssetDescriptor~T~ : assetDescriptor + AssetTypeInfo --> AttributeDescriptor~T~ : attributeDescriptors + AttributeDescriptor~T~ --> ValueDescriptor : type + AttributeDescriptor~T~ --> MetaMap : meta + AttributeDescriptor~T~ --> ValueConstraint : constraints + MetaMap --> ValueConstraint : holds via MetaItemType + ValueConstraint <|-- ConcreteSubtypes : dynamic subtypes ``` --- From 7862c160a16bbc385c541ae5b5a6bb506f136c40 Mon Sep 17 00:00:00 2001 From: Rich Turner <7072278+richturner@users.noreply.github.com> Date: Wed, 17 Jun 2026 16:31:32 +0100 Subject: [PATCH 5/7] Fix typos --- docs/architecture/asset-validation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/architecture/asset-validation.md b/docs/architecture/asset-validation.md index 0f8c33c8..b4ffbe37 100644 --- a/docs/architecture/asset-validation.md +++ b/docs/architecture/asset-validation.md @@ -130,7 +130,7 @@ When valueDescriptor.arrayDimensions > 0, constraints are applied to each elemen ## `validateValueConstraint` (the leaf evaluator) -'''java +```java // ValueUtil.java:1051 public static boolean validateValueConstraint(..., ValueConstraint valueConstraint, Object value) { if (!valueConstraint.evaluate(value, now)) { @@ -141,7 +141,7 @@ public static boolean validateValueConstraint(..., ValueConstraint valueConstrai } return true; // valid } -''' +``` ## Constraint source summary From a9b595811c28c8a08b8cf6e930902085a1f23523 Mon Sep 17 00:00:00 2001 From: Rich Turner <7072278+richturner@users.noreply.github.com> Date: Wed, 17 Jun 2026 16:32:13 +0100 Subject: [PATCH 6/7] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- docs/architecture/asset-validation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/architecture/asset-validation.md b/docs/architecture/asset-validation.md index b4ffbe37..da1cd190 100644 --- a/docs/architecture/asset-validation.md +++ b/docs/architecture/asset-validation.md @@ -116,8 +116,8 @@ ValueUtil.validateValue( private static boolean validateConstraints(Integer dimensions, ValueConstraint[] constraints, ..., Object value) { if (dimensions == null || dimensions == 0 || value == null) { // Leaf: evaluate each constraint directly against the value - return Arrays.stream(constraints).map(c -> validateValueConstraint(..., c, value)) - .anyMatch(valid -> !valid); + return Arrays.stream(constraints) + .anyMatch(c -> !validateValueConstraint(..., c, value)); } else { // Array: recurse into each element, decrementing dimension count return Arrays.stream((Object[]) value) From 8054e1753a6e1749495ca2b69f07110244a934b1 Mon Sep 17 00:00:00 2001 From: Rich Turner <7072278+richturner@users.noreply.github.com> Date: Fri, 19 Jun 2026 10:45:32 +0100 Subject: [PATCH 7/7] Fix escaping --- docs/architecture/asset-validation.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/architecture/asset-validation.md b/docs/architecture/asset-validation.md index da1cd190..3e6cadb5 100644 --- a/docs/architecture/asset-validation.md +++ b/docs/architecture/asset-validation.md @@ -145,11 +145,11 @@ public static boolean validateValueConstraint(..., ValueConstraint valueConstrai ## Constraint source summary -| Source | Where defined | Applied via | -|--------|--------------|-------------| -| ValueDescriptor.constraints[] | On the value type (e.g. ValueType.NUMBER) | validateConstraints() with array recursion | -| AttributeDescriptor.constraints[] | On the static attribute definition | validateConstraints() with array recursion | -| AttributeDescriptor.meta[CONSTRAINTS] | In the descriptor's MetaMap, key "constraints" | Direct per-constraint loop | -| metaHolder.meta[CONSTRAINTS] | On the live Attribute instance's MetaMap | Direct per-constraint loop | - -The key distinction between sources 3 and 4 is that source 3 comes from the static descriptor (set by the developer defining the asset model), while source 4 comes from the live attribute (set at runtime, e.g. by a user configuring constraints via the UI or API). Both are checked with MetaItemType.CONSTRAINTS — the same MetaItemDescriptor. +| Source | Where defined | Applied via | +|-----------------------------------------|--------------|-------------| +| ValueDescriptor.constraints\[\] | On the value type (e.g. ValueType.NUMBER) | validateConstraints() with array recursion | +| AttributeDescriptor.constraints\[\] | On the static attribute definition | validateConstraints() with array recursion | +| AttributeDescriptor.meta\[CONSTRAINTS\] | In the descriptor's MetaMap, key "constraints" | Direct per-constraint loop | +| metaHolder.meta\[CONSTRAINTS\] | On the live Attribute instance's MetaMap | Direct per-constraint loop | + +The key distinction between sources 3 and 4 is that source 3 comes from the static descriptor (set by the developer defining the asset model), while source 4 comes from the live attribute (set at runtime, e.g. by a user configuring constraints via the UI or API). Both are checked with MetaItemType.CONSTRAINTS — the same MetaItemDescriptor\.