Files
UA-.NETStandard/plans/ctt-issues.md
romanett 78e66f3473 Aggregates: TreatUncertainAsBad never applies to bounding values; DurationGood/Bad count Good/Bad regions only (#4702)
# Description

Server-side aggregate fixes that follow three answers from the OPC
Foundation on Mantis:
- [11462](https://mantis.opcfoundation.org/view.php?id=11462)
(~0025840): TreatUncertainAsBad does not apply to bounding values.
- [11425](https://mantis.opcfoundation.org/view.php?id=11425)
(~0025847): DurationGood counts only Good regions and DurationBad only
Bad regions, and TreatUncertainAsBad does not apply.
- [11426](https://mantis.opcfoundation.org/view.php?id=11426)
(~0025852): Minimum, Maximum, the ActualTime variants and Range look at
Uncertain values as if they are Good.

The principle in 11425 and 11426 is that the aggregate definition wins
over TreatUncertainAsBad. The CTT-side defects are filed as
[11487](https://mantis.opcfoundation.org/view.php?id=11487).

### Bounding values (`AggregateCalculator`,
`StartEndAggregateCalculator`)
- Simple (§3.1.9) and Interpolated (§3.1.8) Bounding Values are formed
from **non-Bad** raw values (`IsBoundCandidate`), whatever
TreatUncertainAsBad is.
- An Uncertain raw value before or after the timestamp makes the bound
`UncertainDataSubNormal`.
- A raw value exactly at the timestamp is the bound and keeps its own
status.
- Only a **Bad** value after a Simple bound reverts to stepped
interpolation.
- StartBound/EndBound keep an Uncertain bound instead of `Bad_NoData`
(Tables 76/77).
- WorstQuality2 now includes an Uncertain start bound, as §5.4.3.36
requires ("the server shall ignore the TreatUncertainAsBad for this
aggregate").

### How aggregates use the values (unchanged rule, two gaps closed)
TreatUncertainAsBad still governs how an aggregate uses the values
inside the interval, including an Uncertain bound.
- `GetRegionsInValueSet` now also converts an Uncertain **first** region
to Bad for Simple-bound aggregates. TimeAverage2/Total2 therefore omit
it like any other Uncertain region (§5.4.3.7, Table 55 "Bound
Uncertain").
- TimeAverage/Total (Interpolated bounds) still use an Uncertain bound
and report Uncertain (Table 54).
- TimeAverage2/Total2 return the PercentTime status `Bad` instead of
`Bad_NoData` for an interval that has data but only omitted data
(§5.4.3.2.1).
- Minimum2/Maximum2 are unchanged: an Uncertain bound is a candidate
only when TreatUncertainAsBad is false (§5.4.3.15).

### NumberOfTransitions (`CountAggregateCalculator`)
§5.4.3.24 speaks only of "non-Bad" values, and Table 72 says "Bound
Uncertain: Use as value". So:
- Uncertain values are counted and can be the previous value, whatever
TreatUncertainAsBad is.
- The previous value is the nearest non-Bad value before the interval
(the early bound).

This reverses an earlier change that made NumberOfTransitions honour
TreatUncertainAsBad.

### Minimum, Maximum, MinimumActualTime, MaximumActualTime, Range
(`MinMaxAggregateCalculator`)
Per the 11426 answer:
- Every non-Bad value is a candidate, whatever TreatUncertainAsBad is.
- An Uncertain extremum is returned with `UncertainDataSubNormal` plus
Calculated. ActualTime keeps the timestamp of the first such value.
- Otherwise the status is Good unless the interval contains Bad values
(then `UncertainDataSubNormal`); it is `BadNoData` if all values are
Bad.
- Range is max − min over the non-Bad values. It is Uncertain if either
extremum is Uncertain or Bad values exist.

Before this change the server ignored an Uncertain value beyond the Good
extremum and returned the Good one with an Uncertain status.
Minimum2/Maximum2/Range2 are unchanged, because §5.4.3.15 ties them to
TreatUncertainAsBad explicitly.

### DurationGood/DurationBad/PercentGood/PercentBad
(`StatusAggregateCalculator`)
- A region is Good only if its data point is Good, and Bad only if it is
Bad.
- Uncertain counts for neither (§5.4.3.31–34).
- The first region still takes the status of the raw value at or before
the start.

### Tests
- New `Aggregates/BoundingValueTreatUncertainAsBadTests`: DeltaBounds,
StartBound/EndBound, TimeAverage2/Total2, Interpolative,
NumberOfTransitions (forward and reverse),
DurationGood/Bad/PercentGood/Bad.
- New Min/Max test for Bad-only and Bad-next-to-Good intervals.
- Updated tests that encoded the old interpretation:
  - Interpolative with an Uncertain raw value at the start: 2 tests.
  - Duration/Percent Good/Bad: 2 tests.
  - NumberOfTransitions with TreatUncertainAsBad: 2 tests.
- Minimum/Maximum/Range with an Uncertain extremum or only Uncertain
values: 2 tests in Server.Tests, 1 in History.Tests.
- `ProcessedReadUsesAdvertisedNodeAggregateDefaultsAsync`: it now probes
the TreatUncertainAsBad default with Count, because PercentGood no
longer depends on it.

### CTT verification
CTT 1.05.513 with the patched scripts (c3674ff, which encode the
11462/11425/11426 answers), Aggregates part (p06):

| Server | Errors | Warnings | Pass | NotImpl |
| --- | --- | --- | --- | --- |
| master fa06c66db | 185 | 1 | 1078 | 3 |
| this branch (854769a0c) | **0** | 2 | 1262 | 3 |

The 185 errors on master break down as:
- Bounds: DeltaBounds 9, StartBound 12, EndBound 12, WorstQuality2 11,
TimeAverage2 5, Total2 5, Interpolative 1.
- Duration*/Percent*: DurationBad 18, PercentBad 18, DurationGood 4,
PercentGood 4.
- Minimum/Maximum/MinimumActualTime/MaximumActualTime/Range: 70.
- NumberOfTransitions: 16.

The 2 warnings on this branch are ActivateSession response-delay
warnings in `initialize.js`, caused by machine load.

Alarms & Conditions (p18) failed intermittently on PR builds: in the
Refresh2, Shelving and Exclusive/Non-Exclusive Level CUs, subscriptions
were lost or filtered events were missing. The same clean build of
854769a0c failed once (3 errors) and passed once (0 errors). The PR with
`src/Opc.Ua.Server/Aggregates` reverted passed, and master passes too.
These failures are A&C flakiness on a heavily loaded shared machine and
are not caused by this PR, whose code runs only in HistoryRead and
aggregate-filter MonitoredItems.

Full 23-part CTT run against this branch (854769a0c, patched scripts
c3674ff/c341918): **1 error in total**, GDS Application Directory
`019.js`, a known CTT native-stack defect (Mantis 11409) that also fails
on master. Aggregates (p06) and Alarms & Conditions (p18) have 0 errors,
and no test case went from Pass to Error compared with master.

### Reviewer notes
- §3.1.7 says *"In all cases the TreatUncertainAsBad (see 4.2.1.2) flag
is used to determine whether Uncertain values are Bad or non-Bad."* This
branch follows the answer to 11462, which states that the flag does not
apply to bounding values. This is documented in `plans/ctt-issues.md`
(U4).
- TimeAverage2/Total2 still apply TreatUncertainAsBad to an Uncertain
start bound when they use it: Table 55 says "Bound Uncertain: Treated as
any other Uncertain value", and the definition omits non-Good data. This
includes a start bound that is Uncertain only because the raw value
after it is Bad.
- Not in scope: `CastToOriginalType` uses `Convert.ToInt32(double)`
(banker's rounding), whereas Part 4 §7.7.3 says to add 0.5 and truncate.
The two differ only at .5 and for negative values.

## Related Issues

- Mantis 11462, 11425 (answers implemented), 11487 (CTT side)

## Checklist

- [x] I have signed the
[CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf)
and read the
[CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md)
doc.
- [x] I have added tests that prove my fix is effective or that my
feature works and increased code coverage.
- [x] I have added all necessary documentation.
- [x] I have verified that my changes do not introduce (new) build or
analyzer warnings.
- [ ] I ran **all** tests locally using the **UA.slnx** solution against
at least .net **framework** and .net **10**, and all passed.
(Aggregate/Historian tests of Opc.Ua.Server.Tests and
Opc.Ua.History.Tests on net10.0; net48 build only)
- [ ] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [ ] I have addressed **all** PR feedback received.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 21:49:13 +02:00

43 KiB
Raw Permalink Blame History

CTT (Compliance Test Tool) failing test cases

Test cases of CTT 1.05.06 (scripts 1.05.513) that fail, warn or skip against the OPC Foundation .NET reference server (ConsoleReferenceServer --ctt) because of a CTT defect, with a short abstract and the Mantis issue that tracks it. The details are in Mantis. Defects that are not filed yet, open questions and CTT project configuration notes follow the tables. The procedure for running the CTT is in ctt-testing.md.

  • "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–C57, 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.

Filed in Mantis

Aggregates

Id Failing test Abstract Mantis
3 Aggregate – Base 002-01.js…002-04.js (every Aggregate CU) The multi-node path of HAAggregateHelper.js dereferences possibleNodeId without a guard. 11251
4 Aggregate – Base Err-004.js Sends an equal-time request when the ProcessingInterval setting is blank. 11252
C1 Aggregate – Base 003-01.js…003-04.js, 004-01.js…004-04.js, Err-004.js; Aggregate – NumberOfTransitions 003-02.js, 004-02.js (TreatUncertainAsBad = False) The harness always sends UseServerCapabilitiesDefaults = TRUE, so the server uses its defaults while the CTT applies the test configuration. 11420
C2 Aggregate – AnnotationCount The CTT counts raw values instead of Annotations. 11421
C3 Aggregate – WorstQuality2 The CTT also includes the end bound. 11422
C4 Aggregate – DurationInStateZero / DurationInStateNonZero The CTT reports Bad below PercentDataBad. 11423
C5 Aggregate – DurationGood/Bad, PercentGood/Bad 002-01.js…002-04.js The CTT truncates durations to whole milliseconds. 11424
C6 Aggregate – DurationGood, PercentGood The CTT ignores the raw value before the interval for the status of the first region. 11425
C48 Aggregate – Minimum, Maximum, MinimumActualTime, MaximumActualTime 001-02.js… The CTT ignores Uncertain values beyond the Good extremum and expects Good instead of UncertainDataSubNormal. 11426
C49 Aggregate – Minimum, MinimumActualTime, MaximumActualTime The CTT does not set the Calculated bit when non-Good values make the status Uncertain. 11427
U2 Aggregate – Minimum2 (reverse reads) The CTT clears the Calculated bit when the minimum is the End bound at the early end of a reverse interval (Part 11 §6.5.4.2). 11461
U4 Aggregate – DeltaBounds, StartBound, EndBound, WorstQuality2, TimeAverage2, Total2, Interpolative (TreatUncertainAsBad = True) The CTT applies TreatUncertainAsBad to bounding values: an Uncertain raw value before or at a bound removes the bound, and DeltaBounds treats an Uncertain bound as Bad (answer to 11462). 11487

Historical Access

Id Failing test Abstract Mantis
2 Historical Access Read Raw initialize.js Accesses CUVariables.ArrayItems without its isDefined guard. 11249
6 Historical Access Read Raw 004.js Rejects correct reverse ordering. 11263
7 Historical Access Read Raw 014.js Indexes a nonexistent second node result. 11264
8 Historical Access Read Raw 019.js Bypasses the CTT test harness. 11265
10 Historical Access Read Raw 012.js Expects BadIndexRangeNoData at the wrong level and uses the first array item to calculate array sizes. 11267, 11273
16 Historical Access Read Raw 013.js Reuses continuation points after changing the IndexRange. 11257
C7 Historical Access Read Raw Err-013.js The message describes the operation-level BadContinuationPointInvalid as a ServiceResult. 11428
C8 Historical Access Read Raw Err-019.js The messages at lines 25 and 43 use an undefined loop variable. 11429
C45 Historical Access Read Raw Err-025.js Expects BadNotSupported for a non-historizing node; the test case is obsolete and will be removed. 11347, 11353

Address Space, Base Information, Attribute and View Services

Id Failing test Abstract Mantis
1 Base Info State Machine Instance 001.js The GeneratesEvent target validation uses the wrong helper. 11248 (duplicate of 11125)
5 AliasName Hierarchy 002.js References an undefined variable. 11262
11 Attribute Read 026.js, 032.js, 034.js, 036.js; Attribute Write Index 007.js The array helpers support neither NodeId[] nor StatusCode[]. 11261, 11250
12 Base Info Core Structure 2 001.js The error message cites the UA 1.04 reference model. 11268
13 Base Info Core Structure 2 001.js ConformanceUnits is tested as a scalar. 11269 (duplicate of 11144)
14 Base Info Core Structure 2 001.js Reads TransactionDiagnostics before any transaction and does not accept BadOutOfService. 11256
C15 Base Info Core Structure 2 001.js The Organizes check in InfoFactory.js dereferences an undefined sourceTypeNodeId for View sources. 11435
15 Base Info SemanticChange 001.js Decodes the Changes array as one ExtensionObject. 11093
C13 Base Info Currency 004.js toCurrencyUnitType() drops the CurrencyUnit Exponent. 11434
C46 Address Space Atomicity 001.js (skips) Examines only the first 10000 variables sorted by NodeId string, and its bit test detects only NonatomicRead. 11446

Monitored Item, Subscription and Session Services

Id Failing test Abstract Mantis
C9 Monitor Value Change V2 042.js Computes indexValue from itself, and the failure message cannot identify the missing item. 11430
C17 Monitor Basic 039.js Calls getMatrixValues without including its library. 11097
18 Subscription Durable 008.js Misspells MoreNotifications and does not drain the queue. 11259
C38 Subscription Durable 012.js Ignores a denied diagnostics Browse (BadUserAccessDenied) and then reads an empty node list. 11445
19 Subscription Minimum 02 020.js Accepts unrelated audit events. 11260
C50 Security None 007.js, Security Basic 256 Sha256 005.js, Subscription Publish Basic cleanup.js (warnings) CloseSession stamps the request before the CTT stops the Session's SessionThread (about 550 ms), so the delay warning blames the server. 11454

Node Management, Discovery and Security

Id Failing test Abstract Mantis
9 Node Management Add Node Err-008.js Tests duplicate NodeIds while client-specified NodeIds are disabled. 11266
C34 Node Management Add Node 001.js–003.js, Err-003.js, Err-005.js, Err-008.js (with RequestedNodeId enabled) RequestedNewNodeId() ignores RequestedNodeId_Namespace and always requests namespace 1. 11443
C16 Discovery Get Endpoints 003.js AcceptedProfileUris omits the WebSocket transport profiles. 11436
C35 Security User Anonymous 002.js initialize.js selects the opc.wss endpoint, which the CTT cannot open. 11102
C37 Session Base Err-002.js, Err-005.js, Err-022.js Send CreateSession with the opc.wss EndpointUrl, whose host name the CTT reads as empty. 11102
C36 Security User Name Password 2 015.js FindTokenType in UaE.js rewrites the cached endpoints, so identical UserTokenPolicies look different. 11444 (follows 11258)

GDS

Id Failing test Abstract Mantis
C19 Application Directory 060.js, 067.js, 069.js Register an ApplicationUri that is not a valid URI. 11438
C20 Application Directory 060.js, 065.js; Query Applications 011.js, 016.js Evaluate the Like patterns [_] and [%] as "contains". 11410
C21 Application Directory 074.js; Query Applications 025.js Evaluate %[^f-h]% and %[^w-y]% as "contains none of". 11411
C22 Application Directory 078.js, 079.js callQueryServers() dereferences the output arguments of a failed Call. 11407
C23 Application Directory 018.js (sometimes 011.js, 028.js) Selects ActionTimestamp instead of ActionTimeStamp and creates the audit item with queue size 1. 11439
C24 Application Directory 019.js The batched RegisterApplication Call of step 3 never reaches the server. 11409
C25 Application Directory 010.js Dereferences the ApplicationId of a rejected registration. 11106, 11408
C26 Application Directory 012.js, 032.js Require ServerCapabilities for a Server record. 11441
C27 Application Directory 027.js Changes a Server that has a DiscoveryUrl into a Client. 11442
C29 Application Directory 005.js The random ApplicationUri is cut at its first NUL, so both calls are identical. 11406
C30 Query Applications 036.js Expects rcp+ DiscoveryUrls the test never registered. 11412
C31 Query Applications 038.js Treats applicationType 3 as invalid. 11413
C51 Application Directory 066.js, 079.js; Query Applications 039.js (pass only because the server returns NA records) Register the reference Servers with ServerCapabilities NA and expect them in query results, which OPC 10000-12 §6.5.10/§6.5.11 excludes; with the exclusion 40 test cases fail. 11458
C57 Application Directory 011.js, 018.js, 028.js Expect the ApplicationRegistrationChangedAuditEvents on the Session of the DiscoveryAdmin user, but the GDS NodeSet grants ReceiveEvents on that EventType only to SecurityAdmin (OPC 10000-3 §8.55); the subscription belongs on a second Session of the SecurityAdminUser. 11482

Alarms and Conditions

Id Failing test Abstract Mantis
C10 A & C Alarm Test_002.js; A & C Confirm Test_001.js (depends on the alarm phase) ValidateRetain evaluates Retain from the main branch only. 11431
C11 A & C Alarm Test_004.js Calls ReadHelper re-entrantly from the alarm callback, which fails client-side with BadInvalidState. 11432
C12 A & C Enable Test_002.js Passes four arguments to the three-argument AddMessage, so the reason text is lost. 11433
C18 A & C Acknowledge, A & C Confirm (warnings) The recommended TwoStateVariable texts are not found for locale en-US. 11437
C39 A & C Comment Test_001.js–Test_004.js (skip); A & C Enable Test_002.js GetCallTime() returns an unset call time. The wrapped 32-bit difference lets the Comment cases pass by accident for about 25 of every 50 days (2026-09-23 to about 2026-10-18). 11447
C40 A & C Exclusive/Non-Exclusive Limit/Level Test_003.js–Test_006.js initialize.js creates the filter subscriptions on a CU session that timed out during the initial event capture. 11448
C41 A & C Alarm Test_002.js (runs to the maximum test time) AcknowledgeableConditionType never gets a result. 11449
C42 A & C Enable Test_003.js (runs to the maximum test time) Depends on all alarm types going active within one tenth of the Alarm Cycle Time. 11450
C44 A & C CertificateExpiration (a --close --hidden run never exits) initialize.js opens a modal dialog. 11453
C52 A & C Refresh, Refresh2, Shelving (BadSubscriptionIdInvalid when the CPU is saturated) Refresh Err_004.js and Refresh2 Err_003.js leave 10 subscriptions each on the shared alarm session, and ShutdownItem() leaves one per Refresh test case. 11459
C53 A & C (alarm thread) StartThreadPublish.js lines 28–29 default misspelled properties, so MaximumPublishCalls/MaximumOutstandingCalls are sent undefined. 11460

Not filed

  • C28. GDS Application Directory 029.js, 038.js, 039.js expect BadInvalidArgument for a null or unknown ApplicationId; the server returns BadNotFound, the only code OPC 10000-12 §6.5.7/§6.5.9 define for an unknown ApplicationId. Held back: the GDS server behavior is to be reviewed first.
  • C32. Monitor Basic 038.js warns when a requested SamplingInterval of 0 is revised to 0, unless the project setting Fastest Sampling Interval Supported is 0. The nodes declare MinimumSamplingInterval 0 and MinSupportedSampleRate is 0, so 0 is correct (Part 4 §7.21). The setting cannot be lowered because monitoredItem.js uses it as the default sampling interval of every MonitoredItem. Held back: relevance unclear.
  • C43. A & C Enable Err_004.js calls Disable, Disable and Enable for every event of a condition without per-condition state, which feeds itself (up to about 850 events in 15 s). In 8 of 28 Enable runs the CTT alarm thread then returned no events for the rest of the CU, although the server sent and the CTT acknowledged them, and the remaining test cases ran to 3 × Alarm Cycle Time. Held back: needs more investigation.
  • C54. GDS AliasName Discovery 002.js, 004.js, 006.js, 007.js, 009.js, 011.js, 012.js, 014.js, 015.js compare each AliasFor target the GDS replicated with the target read from the AliasName source using UaExpandedNodeId.equals() (AssertAliasNamesReplicated, library/GDS/AliasNameDiscoveryHelpers.js line 92). The source references its own node (ns=5;i=10012, ServerIndex 0); the GDS has to reference that node on the source (OPC 10000-17 Annex C.1: "the ExpandedNodeId of all of the referenced NodeIds and the ServerUri of the Server containing the NodeId"; OPC 10000-4 §7.16: ServerIndex is the index in the local ServerArray, 0 is the local Server), so a conformant GDS never matches: "AliasFor reference to 'AliasNameVar_Topic_1' (NodeId: ns=5;i=10006, NamespaceUri: , ServerIndex: 0) is missing on replicated element 'Topic_Alias_1'". Fix: compare the identifier and the namespace URI of the source target with the GDS target, and require the GDS target's ServerIndex to select the source's ApplicationUri in the GDS ServerArray. With that comparison (a diagnostic script copy compared the identifier and required a ServerIndex above 0) and the C55 fix, all 15 test cases pass. Draft Mantis text:

    GDS AliasName Discovery: AssertAliasNamesReplicated compares AliasFor targets of different Servers with equals(). AssertAliasNamesReplicated() checks the replicated AliasFor references with currentFindReference.NodeId.equals( currentSearchReference.NodeId ). currentFindReference was browsed on the AliasName source, where the target is a local node (ServerIndex 0, namespace index of the source). On the GDS the same target is a node of another Server: Part 17 Annex C.1 requires the GDS to provide "the ExpandedNodeId of all of the referenced NodeIds and the ServerUri of the Server containing the NodeId", so the reference carries the ServerIndex of the source in the GDS ServerArray (Part 4 §7.16) and, because the GDS namespace table differs from the source's, the namespace URI instead of the source's namespace index. The two ExpandedNodeIds can never be equal, and every test case that registers a source fails with "AliasFor reference to ... is missing on replicated element". Please compare the identifier, resolve the source's namespace index to its URI (source NamespaceArray) and compare it with the GDS target's NamespaceUri, and check that the GDS ServerArray entry at the target's ServerIndex is the source's ApplicationUri.

  • C55. GDS AliasName Discovery 001.js, 005.js, 010.js, 013.js describe the check as "No Alias name instances associated with remote alias servers are present under TagVariables/Topics" but fail on every AliasNameType instance in the folders, also on aliases the GDS defines for its own nodes. A GDS that is also an AliasName Server (the reference server in --ctt mode: TIC101_PV, Devices.Heater_Power, ServerEvents, …) can never pass, although OPC 10000-17 does not forbid local aliases on a GDS. Fix: count only aliases with an AliasFor reference to another Server (ServerIndex > 0). With that filter the four test cases pass against the reference server; a GDS without own aliases passes unchanged. Draft Mantis text:

    GDS AliasName Discovery 001/005/010/013: "no remote aliases" check fails on the GDS's own aliases. The test cases expect "No Alias name instances associated with remote alias servers" under TagVariables (i=23479) and Topics (i=23488) before anything is registered, but report every AliasNameType instance 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 BadNoData for valid Boolean/String StartBound/EndBound. Related: 11274. Only numeric values can be interpolated with a sloped line (Part 13 §5.4.2.3, Table 50), so the server uses stepped interpolation for Boolean, String and other non-numeric values whatever the Stepped property says (a String bound between "23" and "24" is "23", not "23.8").
    • Int32 conversion: the CTT truncates interpolated bounds of Int32 nodes, the server keeps them real (Part 13 §3.1.8) and rounds them (add 0.5 and truncate, Part 4 §7.7.3) only for aggregates that return the raw data type (Interpolative, StartBound/EndBound, DeltaBounds, Minimum2/Maximum2/Range2, MinimumActualTime2/MaximumActualTime2), for example 24 vs 23. TimeAverage, TimeAverage2, Total and Total2 return a Double and use the real bound: a ramp from 0 to 23.8 averages 11.9; the CTT's truncated bound gives 11.8866, and Total 005-03.js (end bound 2.38 vs 2) gives 28.3196 vs 27.598. Corrections that rewrite the expected values of the four aggregates, and of Range2 when the start bound is followed by a non-Good value with TreatUncertainAsBad (the native selection does not use that Uncertain bound), make all 78 TimeAverage/TimeAverage2/Total/Total2 and 4 Range2 Int32 test cases pass except the U4 cases.
    • MinimumActualTime2/MaximumActualTime2: with a sloped End bound the server returns the bound at EffectiveEndTime (Part 13 §§5.4.3.17–.18); the CTT selects an earlier raw value.
  • Aggregate Bad data search reads forward. HAAggregateHelper.GetBadData reads 120 raw values "backward" from the configured StartOfBadData* time with EndTime = 0001-01-01 (new UaDateTime()). That is the null DateTime, so the request has only a StartTime and the server reads forward (Part 11 §6.5.3); the helper then takes the last of those values as the earliest. Its final read therefore spans the 120th value after the configured time to the 120th value after the first non-Good value it finds, and the Bad block must lie inside that range (see Bad data entries for aggregates).

Needs clarification

U4. TreatUncertainAsBad and bounding values (answered)

The clarification request 11462 is answered (~0025840): TreatUncertainAsBad does not apply to bounding values; they are calculated only as §3.1.7–§3.1.9 describe. The server and the CTT both applied it, in different places, so DeltaBounds 003-01.js…008-01.js and TimeAverage2/Total2 003-xx/004-xx differed. §3.1.7 also says "In all cases the TreatUncertainAsBad … flag is used to determine whether Uncertain values are Bad or non-Bad"; the answer is read as covering that sentence.

  • Server (fixed). Simple and Interpolated Bounding Values are formed from non-Bad raw values whatever TreatUncertainAsBad is (AggregateCalculator.IsBoundCandidate): an Uncertain raw value before, at or after the bound makes the bound UncertainDataSubNormal (a raw value at the bound keeps its own status), and only a Bad value after a Simple bound reverts to stepped. StartBound/EndBound keep an Uncertain bound (Tables 76/77) and WorstQuality2 includes it (§5.4.3.36 ignores TreatUncertainAsBad). How an aggregate uses an Uncertain bound still follows TreatUncertainAsBad: TimeAverage2/Total2 omit it like any other Uncertain value (§5.4.3.7, Table 55; the first region was not converted before), and report Bad, not BadNoData, for an interval that holds only omitted data (§5.4.3.2.1); Minimum2/Maximum2 ignore it as a candidate (§5.4.3.15). TimeAverage/Total use an Uncertain Interpolated bound and report UncertainDataSubNormal (Table 54).
  • CTT. The native calculation applies TreatUncertainAsBad to the raw values at and before a bound and treats an Uncertain bound as Bad in DeltaBounds: filed as 11487.
  • DurationGood/DurationBad/PercentGood/PercentBad (fixed). Paul's note on 11425 (~0025847): the bound is the stepped Simple bound, TreatUncertainAsBad does not apply, DurationGood counts only Good and DurationBad only Bad regions (an Uncertain region counts for neither), and the first region takes the status of the raw value at or before the start (§5.4.3.31–34). The server counted Uncertain regions as Good or Bad depending on TreatUncertainAsBad.
  • Aggregate definitions win over TreatUncertainAsBad (fixed). Paul's note on 11426 (~0025852): Minimum, Maximum, MinimumActualTime, MaximumActualTime and Range follow OPC Classic. Uncertain values are looked at as if they are Good, so an Uncertain extremum is returned with UncertainDataSubNormal; the status is otherwise Good unless Bad values exist (UncertainDataSubNormal), and BadNoData if all values are Bad. The server ignored an Uncertain value beyond the Good extremum. NumberOfTransitions follows §5.4.3.24 ("non-Bad") the same way: Uncertain values are counted and can be the previous value, whatever TreatUncertainAsBad is. Minimum2/Maximum2 keep TreatUncertainAsBad, which §5.4.3.15 names explicitly.
  • With the patched scripts corrected accordingly, Aggregates (p06) has 0 errors against the fixed server and 99 against the server before the fix.

U5. TreatUncertainAsBad = false: are Uncertain values Good for the value and for the status?

§4.2.1.2 makes an Uncertain value equivalent to Good when TreatUncertainAsBad is false "unless the Aggregate definition says otherwise", and adds that the value "is still treated as Uncertain when the StatusCode for the result is calculated". §5.4.3.2.1 counts Uncertain regions as Good regions in that case. The definitions of Average, Count, Delta, StandardDeviation* and Variance* speak of Good (or non-Good) values, NumberOfTransitions of Bad values. Since C1 is fixed the CTT sends TreatUncertainAsBad = false in 003-02.js, 003-03.js, 004-02.js, 004-03.js, and 34 test cases differ:

Aggregates Server CTT
Average, Count, Delta, StandardDeviation*, Variance* includes the Uncertain values (§4.2.1.2), status Good when no Bad value is in the interval (§5.4.3.2.1) excludes them (the definitions say Good), status UncertainDataSubNormal (§4.2.1.2 note)
NumberOfTransitions same value; status Good same value; status UncertainDataSubNormal
Range ignores an Uncertain value beyond the Good extremum and reports UncertainDataSubNormal (§5.4.3.14) Good (as C48)

Until then Average and Count used the Good values only for the value but counted Uncertain as Good for the status; they now include the Uncertain values like StandardDeviation and NumberOfTransitions already did. Not filed; the open part (does the definition's "Good" override §4.2.1.2, and which rule sets the status) belongs to the same clarification as U4.

CTT project configuration notes

Tests skipped because of reference server sample-data gaps or missing CTT project settings are tracked in #4479.

  • Aggregate ProcessingInterval. samples/UAReferenceServer.ctt.xml sets /Server Test/NodeIds/Static/HA Profile/Aggregates/ProcessingInterval to 1; keep it positive (see issue 4).
  • Bad data entries for aggregates. 005-05.js/005-06.js need an explicit Bad data entry. Without it, HAAggregateHelper.GetRequestEntry falls back to the start entry ("Bad Data Entry no found, using start data") or throws ("GetRequestEntry failed due to incorrect test configuration"). GetStartBadDataTime (HAAggregateHelper.js line 1023) reads .../Aggregates/StartOfBadData<Name> as an absolute time and the helper looks for a block of at least two consecutive non-Good values. The reference server seeds every history node with 1001 values relative to its start time (10 s apart; index mod 10 = 7 is BadDataUnavailable, index mod 10 = 9 is UncertainSubstituteValue, the rest Good) and every scalar history node also with 400 values from 2026-01-01T00:00:00Z (value = index, source timestamp + 1.234 s): index 0–149 repeat the pattern (the CTT uses the first 120 values as the start data of the other test cases), 150–267 are Good, 268 is Bad, 269–274 Good, 275–279 the Bad block, 280–399 the pattern again. samples/UAReferenceServer.ctt.xml sets all ten StartOfBadData* settings to 2026-01-01T00:25:01.234Z (index 150). The layout follows the forward-reading search (see Aggregate Bad data search reads forward): 120 values after index 150 must contain a non-Good value followed by a Good one (268), and the block must lie between index 269 and 387. The 003-xx/004-xx test cases that request "bad or start" data use this block too. The server's StartOfArchive is the fixed date.
  • Reference server settings for #4479. samples/UAReferenceServer.ctt.xml sets the following; a project created from an older template keeps the old values, so copy them over:
    • /Server Test/NodeIds/Static/All Profiles/Scalar/Bool = ns=2;s=Scalar_Static_NonHistorizing_Boolean. Historical Access Read Raw Err-025.js and Delete Value dat-Err-001.js/Err-004.js take the first Static Scalar node as the non-historizing node. The HA Profile and Aggregate Boolean settings stay on Scalar_Static_Boolean.
    • /Server Test/NodeIds/References/Has References of a ReferenceType and SubType = i=2253. The Server object has HasComponent and HasAddIn references. References_HasReferenceTypeAndSubType loses its hierarchical references in the source generator (#4484).
    • /Server Test/NodeIds/NodeClasses/Object = i=2253, so View Basic 2 018.js also sees Method references and covers every NodeClass.
  • Base Info Diagnostics 018-1.js–018-3.js write Server.ServerDiagnostics.EnabledFlag, which the reference server allows only for the SecurityAdmin or ConfigureAdmin role over SignAndEncrypt (the project's sysadmin user). 018-1.js warns "Session diagnostics not available" for the session it creates while diagnostics are disabled; that is expected.
  • Node Management Add Node 002.js adds a Variable with every enabled /Server Test/NodeIds/NodeManagement/SupportedReferences entry. Enable only hierarchical references valid for a Variable target (typically Organizes, HasProperty, HasComponent). Non-hierarchical references and HasSubtype correctly return BadReferenceNotAllowed (Part 4 §5.8.2).
  • DI Base Model. The 14 DI Base Model CUs (the DI ITagNameplate/DI IVendorNameplate units under maintree/OPC UA FX) skip entirely: the reference server has no http://opcfoundation.org/UA/DI/ or http://opcfoundation.org/UA/FX/Data/ namespace and no FxRoot folder. Testing them needs a server that loads the DI and UA FX models. The same applies to every UAFX group (AutomationComponent, Base, FunctionalEntity, FxAsset: 59 CUs): initialize.js reports the missing DI and FX/Data namespaces as errors, UAFX FxRoot 001.js fails, and the rest skips.
  • PubSub Publisher UADP CUs. PubSub Publisher UADP chunking, Defined Ordering and Periodic Fixed Settings abort in initialize.js ("ConfigurePubSubTest(): Failed to upload PubSubConfiguration to server", library/PubSub/PubSubUtilities.js line 1232). The reference server keeps the standard PublishSubscribe object but implements no PubSub publisher: neither the PubSubConfiguration file (Open is not implemented) nor AddConnection/AddPublishedDataItems. Not applicable; deselect the PubSub General group.
  • UserDefinedCU. The CTT's sample custom CU (ProfileSet_Custom.xml) logs "Hello error"/"Hello warning" by design. Deselect it.
  • Discovery. Find Servers Filter 002.js needs at least two servers known to FindServers (an LDS); Find Servers Self 010.js and Get Endpoints 009.js need a multi-homed host or several hostnames.
  • Auditing. Auditing Connections 002.js, 003.js, 008.js, 010.js, 014.js skip when no other test case in the same run produces the audit event they look for. Run the Auditing group together with the service groups whose actions it audits.
  • GDS target server. Application Directory and Query Applications exercise the same code (ApplicationsNodeManager + LinqApplicationsDatabase from src/Opc.Ua.Gds.Server) on the reference server in --ctt mode and on a dedicated GDS, so either target gives the same results. The reference server keeps its GDS database in memory (empty DatabaseStorePath), so every server start begins with an empty directory; a GDS with a JSON database (DatabaseStorePath set, as in the opc.tcp://localhost:58810/GlobalDiscoveryServer sample GDS project) keeps records of aborted earlier runs and changes the record counts every test expects. Delete that file before each run. Run the four GDS CUs in one run: the test cases within a CU depend on the records registered by its initialize.js and earlier test cases (012.js and 019.js unregister and re-register them).
  • GDS AliasName Discovery. The CU tests the GDS AliasName Server Facet (OPC 10000-17 Annex C). The reference server in --ctt mode implements it: Ctt.ReferenceServer.Config.xml sets <EnableAliasNameAggregation>true</EnableAliasNameAggregation> in its GlobalDiscoveryServerConfiguration, so the GDS reads the AliasNames of every Server registered with the ServerCapability ALIAS before RegisterApplication returns. initialize.js always uses the CTT's embedded server as the first source. 005.js–009.js need a second and 010.js–015.js a third AliasName Server (/Server Test/GDS/AliasName Discovery/AliasName Source 1 URL, ... Source 2 URL, plus UserName/Password if they do not allow Anonymous). samples/UAReferenceServer.ctt.xml leaves them empty because the repository has no suitable server: a second reference server is no good source, as it has the same alias and category names as the GDS host (Devices), and the checks match categories by name (see below). Each source must
    • offer Anonymous on an endpoint the CTT selects (it prefers SecurityMode None) and trust the CTT and the GDS,
    • use alias and category names that neither the other sources nor the GDS itself use: after a source is unregistered, CheckReplicationOfOtherCategories(AssertNotReplicated) fails on any category of that name that is still there, although Annex C.3 keeps a category another Server still provides,
    • expose no optional children (FindAliasVerbose, AddAliasesToCategory, LastChange) on its own categories: the check expects every hierarchical reference of a source category on the replicated one, while the GDS exposes FindAlias only on the categories it merges. RegisterApplication warns ("delay in excess of ... ms") because it waits for the source to be read. 001.js, 005.js, 010.js and 013.js fail against the reference server because of its own aliases (C55), and the test cases that check AliasFor references fail on any conformant GDS (C54).
  • GDS LDS-ME Connectivity. initialize.js skips the CU unless QueryApplications with ServerCapabilities = ["LDS"] returns a record: register an LDS/LDS-ME with the GDS first. The reference server does not include an LDS.
  • Node Management client NodeIds. samples/UAReferenceServer.ctt.xml enables /Server Test/NodeIds/NodeManagement/RequestedNodeId with RequestedNodeId_Namespace = 2 (the reference server namespace). That needs the C34 fix: scripts 1.05.513 request NodeIds in namespace 1 regardless of RequestedNodeId_Namespace, and six Add Node test cases fail with BadNodeIdRejected. With unfixed scripts disable the setting; Err-008.js then keeps failing (issue 9).
  • Monitored Item Services manual test cases. Monitor Basic 036.js, Monitor Complex Value 001.js–003.js, Monitor Events 002.js/003.js, Monitor Queueing 013.js/014.js, and the Monitor Complex Event Filter and Monitor QueueSize_ServerMax CUs are Not Implemented (manual or test-lab) in scripts 1.05.513. So are Node Management Add Ref and Delete Ref.
  • Security groups need the CTT PKI, not -a. See ctt-testing.md. With -a every negative certificate test fails spuriously.
  • SecureChannels without a Session. Security None 007.js and Security Basic 256 Sha256 005.js keep channels without a Session idle for about 51 s and expect the newest one to close with Good. The server closes such channels after ChannelLifetime (default 30 s), although Part 4 §5.6.2.1 lets a SecureChannel live until its last token expires; keeping them open would let an unauthenticated client hold a channel for up to the SecurityTokenLifetime. Ctt.ReferenceServer.Config.xml sets ChannelLifetime to 120000 instead, and both test cases pass (005.js keeps the C50 warning).
  • Security General coverage. In scripts 1.05.513, 50 of the 53 CUs contain only Not Implemented test cases (Push/Pull Model, No Application Authentication, Security Administration, Certificate Administration, Default ApplicationInstance Certificate, all Role and User Management CUs, TLS, Time Sync, KeyCredential, broker authentication, ECC, LegacySequenceNumber, Encryption/Signing/Policy Required, SecurityPolicy Support). Automated test cases exist only in Security Certificate Validation and Security None CreateSession ActivateSession (and its 1.0 variant).
  • Security Certificate Validation skips. 004.js skips because the CTT stack cannot send an empty client certificate. 049.js/050.js need a Basic128Rsa15 endpoint for their SHA-1 certificates; the reference server does not offer that deprecated policy (not applicable).
  • Security User Token coverage. Security User Anonymous 003.js needs a secure endpoint without the Anonymous token; the CTT configuration offers Anonymous on every endpoint. UserTokenPolicies are server-wide in the configuration, so this needs another SignAndEncrypt endpoint (for example Aes128_Sha256_RsaOaep) whose policies ReferenceServer.GetUserTokenPolicies filters; not done, because every CU that iterates the endpoints would then see it. Security User Name Password 2 002.js needs a UserName token policy with SecurityPolicy #None on an encrypted endpoint (password sent unencrypted inside the channel); the reference server always encrypts passwords (not applicable). 012.js passes only because /Server Test/Session/LoginNameAccessDenied (username) is not a known user and the server answers unknown credentials with BadUserAccessDenied; the reference server has no user that authenticates but is denied access. Security Invalid user token, the Kerberos, JWT, Authority Profile and Token Unencrypted CUs, and X509 003.js/012.js, are Not Implemented.
  • Alarms and Conditions coverage. The single-case CUs (ConditionClasses, Condition Sub-Classes, Suppression by Operator, Silencing, OutOfService, On-Off Delay, Re-Alarming, First in Group Alarm, Audible Sound, Discrepancy, Trip, A&E Wrapper Mapping, Dialog) contain only manual (Not Implemented) test cases.
  • A & C Shelving coverage. /Server Test/Alarms and Conditions/Chattering Alarms is empty, so UseChatteringAlarms sets IgnoreSkip on every type and Test_003.js–Test_005.js, Test_007.js–Test_010.js and Err_001.js–Err_003.js finish immediately without testing anything. The reference server has no alarm that stays active across transitions, so there is no condition to configure there yet. It would need an alarm source in the sample AlarmNodeManager that oscillates inside the alarm band (for example between 75 and 95 around a HighHigh limit of 90), with its ConditionId in that setting; the other A & C CUs would then see a condition that never clears.
  • A & C Alarm Cycle Time and session timeout. /Server Test/Alarms and Conditions/Alarm Cycle Time sets the initial event capture (1 ×), the maximum time of every collector test case (3 ×) and the Enable Test_003.js refresh delay (1/10). Keep it below /Server Test/Session/RequestedSessionTimeout (ms) when a Limit/Level CU can be the first A&C CU of a run (C40). The template sets 30 s against a 60 s session timeout.
  • Session and Subscription coverage. Manual (Not Implemented) test cases: Subscription Basic 072.js, 073.js; Subscription Multiple 001.js–003.js; Subscription Publish Basic 005.js–007.js, Err-001.js; Subscription PublishRequest Queue Overflow 001.js, 002.js; Subscription Durable 013.js. Subscription Durable StorageLevel High/Medium/Small and Subscription Retransmission Queue contain only NoTestCaseDefined.js. Skipped by the scripts: Subscription Basic 067.js (under Working Group review), Subscription Durable 006.js (server restart) and 009.js (events), Subscription Transfer Err-010.js (no script), Session Base Err-009.js (the CTT has no Kerberos token) and Err-023.js (the server offers SecurityPolicy None, correct for --ctt).
  • Historical Access coverage. Every Historical Access CU except Read Raw contains only NoTestCaseDefined.js in scripts 1.05.513. Insert/Replace/Update/Delete (values and events), Annotations, ServerTimestamp, Modified, Time Instance and Structured Data are not tested.