diff --git a/plans/ctt-issues.md b/plans/ctt-issues.md
index c8f2e2c3ca..7cdf4b4168 100644
--- a/plans/ctt-issues.md
+++ b/plans/ctt-issues.md
@@ -8,7 +8,7 @@ CTT project configuration notes follow the tables. The procedure for running the
- "Resolved / fixed" in Mantis means the fix is in the CTT script repository. It ships with a script
build after 1.05.513, so an installed 1.05.513 still shows the failure.
-- The ids (1–19, C1–C55, U1–U5) are stable references for notes and commit messages; missing ids were
+- The ids (1–19, C1–C56, U1–U5) are stable references for notes and commit messages; missing ids were
withdrawn or no longer fail against the reference server.
- Mantis states were last checked on 2026-09-25.
@@ -167,6 +167,13 @@ CTT project configuration notes follow the tables. The procedure for running the
> found there. A GDS that also exposes AliasNames for its own nodes (its AliasFor targets have ServerIndex 0)
> fails although it aggregated nothing. Please only report AliasNameType instances that have an AliasFor
> reference whose target ExpandedNodeId has a ServerIndex other than 0.
+- **C56. Monitored Items Deadband Filter `Err-006.js`** modifies a MonitoredItem on the first static numeric
+ scalar (a BaseDataVariable without EURange) to a PercentDeadband and accepts only `Good` or
+ `Bad_MonitoredItemFilterUnsupported`. OPC 10000-8 §7.2 limits PercentDeadband to AnalogItems with an EURange,
+ and §7.3.2 Table 61 defines `Bad_DeadbandFilterInvalid` for "a PercentDeadband is not supported, since an
+ EURange is not configured", which the server returns since #4629 (Create and Modify alike). Monitor Basic
+ `Err-022.js` already accepts `Bad_FilterNotAllowed` for non-analog items; the accepted list of `Err-006.js`
+ needs `Bad_DeadbandFilterInvalid`. Draft ready, not filed yet.
- **Aggregate expected-value differences.**
- Non-numeric nodes: status-only aggregates (DurationGood/Bad, PercentGood/Bad, WorstQuality2, DurationInState*)
differ on Boolean/String nodes from numeric nodes with the same status timeline, and the CTT returns
diff --git a/samples/Quickstarts.Servers/ReferenceServer/Model/ReferenceServer.NodeSet2.xml b/samples/Quickstarts.Servers/ReferenceServer/Model/ReferenceServer.NodeSet2.xml
index 5f7ecc1896..7f4667afe1 100644
--- a/samples/Quickstarts.Servers/ReferenceServer/Model/ReferenceServer.NodeSet2.xml
+++ b/samples/Quickstarts.Servers/ReferenceServer/Model/ReferenceServer.NodeSet2.xml
@@ -56318,6 +56318,7 @@ ucclQekzV2XU/r54d++wA0fvXZkJ9Kqh7A93bFFRYA==
ns=1;s=DataAccess_ArrayItemType
ns=1;s=DataAccess_SelectionList
ns=1;s=DataAccess_Currency
+ ns=1;s=Views_Maintenance
i=61
ns=1;s=CTT
@@ -60583,6 +60584,7 @@ ucclQekzV2XU/r54d++wA0fvXZkJ9Kqh7A93bFFRYA==
ns=1;s=Views_Operations
ns=1;s=Views_Engineering
+ ns=1;s=Views_Maintenance
i=61
ns=1;s=CTT
@@ -60590,12 +60592,19 @@ ucclQekzV2XU/r54d++wA0fvXZkJ9Kqh7A93bFFRYA==
Operations
- ns=1;s=Views_Engineering
- ns=1;s=Views_Engineering
+ ns=1;s=Views_Engineering
+ ns=1;s=Views_Maintenance
ns=1;s=Scalar_Static_Mass
i=87
+
+ Maintenance
+
+ ns=1;s=DataAccess
+ i=87
+
+
Engineering
diff --git a/tests/Opc.Ua.Server.Tests/ReferenceServerTest.cs b/tests/Opc.Ua.Server.Tests/ReferenceServerTest.cs
index 4383d2216f..932750dd59 100644
--- a/tests/Opc.Ua.Server.Tests/ReferenceServerTest.cs
+++ b/tests/Opc.Ua.Server.Tests/ReferenceServerTest.cs
@@ -1069,6 +1069,47 @@ namespace Opc.Ua.Server.Tests
Assert.That(references.ToArray().Count(r => r.NodeClass != nodeClass), Is.GreaterThanOrEqualTo(1));
}
+ ///
+ /// Views can only be the source of hierarchical References (Part 3 §5.4), and the
+ /// CTT (Base Info Core Structure 2 001.js) also rejects Views targeted by
+ /// non-hierarchical References.
+ ///
+ [Test]
+ [TestCase("Views_Operations")]
+ [TestCase("Views_Engineering")]
+ [TestCase("Views_Maintenance")]
+ public async Task SampleViewsHaveNoNonHierarchicalReferencesAsync(string identifier)
+ {
+ ushort namespaceIndex = (ushort)m_server.CurrentInstance.NamespaceUris.GetIndex(
+ Quickstarts.ReferenceServer.Namespaces.ReferenceServer);
+ ArrayOf nodesToBrowse =
+ [
+ new BrowseDescription
+ {
+ NodeId = new NodeId(identifier, namespaceIndex),
+ BrowseDirection = BrowseDirection.Both,
+ ReferenceTypeId = ReferenceTypeIds.NonHierarchicalReferences,
+ IncludeSubtypes = true,
+ NodeClassMask = 0,
+ ResultMask = (uint)BrowseResultMask.All
+ }
+ ];
+
+ var requestHeader = (RequestHeader)m_requestHeader.Clone();
+ requestHeader.Timestamp = DateTimeUtc.Now;
+ BrowseResponse response = await m_server.BrowseAsync(
+ m_secureChannelContext,
+ requestHeader,
+ null,
+ 0,
+ nodesToBrowse,
+ RequestLifetime.None).ConfigureAwait(false);
+
+ Assert.That(response.Results, Has.Count.EqualTo(1));
+ Assert.That(response.Results[0].StatusCode, Is.EqualTo(StatusCodes.Good));
+ Assert.That(response.Results[0].References.ToArray(), Is.Empty);
+ }
+
///
/// Test that the ReferenceNodeManager Currency variable exposes its value
/// and its CurrencyUnit property.
diff --git a/tests/Opc.Ua.SourceGeneration.Core.Tests/Generators/NodeStateGeneratorTests.cs b/tests/Opc.Ua.SourceGeneration.Core.Tests/Generators/NodeStateGeneratorTests.cs
index e3b0b5ced9..30aba8d712 100644
--- a/tests/Opc.Ua.SourceGeneration.Core.Tests/Generators/NodeStateGeneratorTests.cs
+++ b/tests/Opc.Ua.SourceGeneration.Core.Tests/Generators/NodeStateGeneratorTests.cs
@@ -1732,6 +1732,63 @@ namespace Opc.Ua.SourceGeneration.Generator.Tests
Assert.That(CompileGeneratedAssembly(files), Is.Not.Null);
}
+ ///
+ /// The source of a GeneratesEvent reference shall be an ObjectType, a VariableType
+ /// or a Method (Part 3 §7.15). An ObjectType or VariableType keeps the references it
+ /// declares, but its Object and Variable instances and instance declarations must not
+ /// inherit them (the CTT flagged
+ /// the GDS AuthorizationServiceType placeholder and the Default service).
+ ///
+ [Test]
+ public void TypeGeneratesEventReferencesAreNotCopiedToObjectInstances()
+ {
+ ITelemetryContext telemetry = NUnitTelemetryContext.Create(logLevel: LogLevel.Error);
+ Dictionary files = GenerateFromModelDesign(
+ "TypeGeneratesEvent.ModelDesign.xml",
+ telemetry);
+
+ string code = files.Single(
+ kv => kv.Key.EndsWith(".NodeStates.ex.g.cs", StringComparison.Ordinal)).Value;
+ const string generatesEvent = "state.AddReference(global::Opc.Ua.NodeId.Create(41u, ";
+ const string alwaysGeneratesEvent = "state.AddReference(global::Opc.Ua.NodeId.Create(3065u, ";
+
+ // The public factory definition; call sites are qualified by "context.".
+ int instanceOf = code.IndexOf(" CreateInstanceOfThingType(", StringComparison.Ordinal);
+ Assert.That(instanceOf, Is.GreaterThanOrEqualTo(0));
+ string instanceOfThingType = code[instanceOf..code.IndexOf(
+ "return state;",
+ instanceOf,
+ StringComparison.Ordinal)];
+
+ Assert.Multiple(() =>
+ {
+ string type = ExtractMethodBody(code, "CreateThingType");
+ Assert.That(type, Does.Contain(generatesEvent));
+ Assert.That(type, Does.Contain(alwaysGeneratesEvent));
+
+ Assert.That(ExtractMethodBody(code, "CreateThingValueType"), Does.Contain(generatesEvent));
+
+ foreach (string instance in new[]
+ {
+ ExtractMethodBody(code, "CreateThingsFolderType_Thing_Placeholder"),
+ ExtractMethodBody(code, "CreateThing1"),
+ instanceOfThingType,
+ ExtractMethodBody(code, "CreateThingType_Value"),
+ ExtractMethodBody(code, "CreateThing1_Value"),
+ ExtractMethodBody(code, "CreateThingValue1")
+ })
+ {
+ Assert.That(instance, Does.Not.Contain(generatesEvent));
+ Assert.That(instance, Does.Not.Contain(alwaysGeneratesEvent));
+ }
+
+ Assert.That(ExtractMethodBody(code, "CreateThingType_Start"), Does.Contain(generatesEvent));
+ Assert.That(ExtractMethodBody(code, "CreateThing1_Start"), Does.Contain(generatesEvent));
+ Assert.That(ExtractMethodBody(code, "CreateExplicitSource"), Does.Contain(generatesEvent));
+ });
+ Assert.That(CompileGeneratedAssembly(files), Is.Not.Null);
+ }
+
private static Dictionary GenerateFromNodeSet(
string nodeSetResource,
ITelemetryContext telemetry)
diff --git a/tests/Opc.Ua.SourceGeneration.Core.Tests/Resources/TypeGeneratesEvent.ModelDesign.xml b/tests/Opc.Ua.SourceGeneration.Core.Tests/Resources/TypeGeneratesEvent.ModelDesign.xml
new file mode 100644
index 0000000000..906960ab02
--- /dev/null
+++ b/tests/Opc.Ua.SourceGeneration.Core.Tests/Resources/TypeGeneratesEvent.ModelDesign.xml
@@ -0,0 +1,104 @@
+
+
+
+
+ http://test.org/UA/TypeGeneratesEvent/
+ http://opcfoundation.org/UA/
+
+
+
+
+
+
+
+ ua:GeneratesEvent
+ ThingEventType
+
+
+
+
+
+
+
+
+
+
+ ua:GeneratesEvent
+ ThingEventType
+
+
+
+
+
+
+ ua:GeneratesEvent
+ ThingEventType
+
+
+ ua:AlwaysGeneratesEvent
+ ua:BaseEventType
+
+
+
+
+
+
+
+ <Thing>
+ ua:Organizes
+
+
+
+
+
+
+
+ ua:Organizes
+ ua:ObjectsFolder
+
+
+
+
+
+
+
+ ua:Organizes
+ ua:ObjectsFolder
+
+
+
+
+
+
+
+ ua:Organizes
+ ua:ObjectsFolder
+
+
+ ua:GeneratesEvent
+ ThingEventType
+
+
+
+
diff --git a/tools/Opc.Ua.SourceGeneration.Core/Generators/NodeStateGenerator.cs b/tools/Opc.Ua.SourceGeneration.Core/Generators/NodeStateGenerator.cs
index 816a6e8da2..bb1647b5d0 100644
--- a/tools/Opc.Ua.SourceGeneration.Core/Generators/NodeStateGenerator.cs
+++ b/tools/Opc.Ua.SourceGeneration.Core/Generators/NodeStateGenerator.cs
@@ -4208,6 +4208,17 @@ namespace Opc.Ua.SourceGeneration
bool isInverse = reference.IsInverse;
+ // The type's GeneratesEvent references describe its instances; an Object or
+ // Variable cannot be their source (Part 3 §7.15), so they stay on the type.
+ if (!isInverse &&
+ reference.DefinedOnType &&
+ reference.SourcePath == node.Path &&
+ root is ObjectDesign or VariableDesign &&
+ IsGeneratesEventReference(reference.ReferenceType))
+ {
+ continue;
+ }
+
if (reference.TargetId != null)
{
if (!m_context.ModelDesign.TryFindNode(
@@ -4302,6 +4313,37 @@ namespace Opc.Ua.SourceGeneration
return references;
}
+ ///
+ /// True for GeneratesEvent and its subtypes such as AlwaysGeneratesEvent.
+ ///
+ private bool IsGeneratesEventReference(XmlQualifiedName referenceTypeId)
+ {
+ var generatesEvent = new XmlQualifiedName("GeneratesEvent", Ua.Types.Namespaces.OpcUa);
+ if (referenceTypeId == generatesEvent)
+ {
+ return true;
+ }
+ if (referenceTypeId == null ||
+ !m_context.ModelDesign.TryFindNode(
+ referenceTypeId,
+ string.Empty,
+ string.Empty,
+ out NodeDesign design))
+ {
+ return false;
+ }
+ for (TypeDesign type = (design as ReferenceTypeDesign)?.BaseTypeNode;
+ type != null;
+ type = type.BaseTypeNode)
+ {
+ if (type.SymbolicId == generatesEvent)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
private List GetAdditionalChildren(NodeToGenerate node)
{
List additionalChildren = [];
diff --git a/tools/Opc.Ua.SourceGeneration.Core/Schema/ModelDesign.cs b/tools/Opc.Ua.SourceGeneration.Core/Schema/ModelDesign.cs
index 3f026f8092..04174d39db 100644
--- a/tools/Opc.Ua.SourceGeneration.Core/Schema/ModelDesign.cs
+++ b/tools/Opc.Ua.SourceGeneration.Core/Schema/ModelDesign.cs
@@ -718,7 +718,7 @@ namespace Opc.Ua.Schema.Model
public XmlQualifiedName TargetId { get; set; }
///
- /// Defined on type
+ /// Declared on a type rather than on an instance or instance declaration.
///
public bool DefinedOnType { get; set; }
diff --git a/tools/Opc.Ua.SourceGeneration.Core/Schema/ModelDesignValidator.cs b/tools/Opc.Ua.SourceGeneration.Core/Schema/ModelDesignValidator.cs
index 0b69bec05b..caa3ec7caf 100644
--- a/tools/Opc.Ua.SourceGeneration.Core/Schema/ModelDesignValidator.cs
+++ b/tools/Opc.Ua.SourceGeneration.Core/Schema/ModelDesignValidator.cs
@@ -6082,6 +6082,7 @@ namespace Opc.Ua.Schema.Model
currentPath,
source.SymbolicId,
source.References[ii]);
+ reference.DefinedOnType = source is TypeDesign;
references.Add(reference);