# Description Nullable reference types are now enabled once, in `common.props`, and every project in the repository builds warning-clean with them. **Configuration** - `<Nullable>enable</Nullable>` lives in `common.props`. All per-project `<Nullable>` settings are removed: 178 project files (enable, annotations, and the `disable` in the three Aot test projects). - 419 per-file `#nullable enable`/`restore` directives and the 4 `#nullable disable` directives are removed. - Kept on purpose: - The 5 `<auto-generated>` schema files (`UANodeSet.cs`, `UA Type Dictionary.cs`, `OPCBinarySchema.cs`, `SecuredApplication.cs`, `PInvokeHelper.cs`) keep `#nullable enable`. The compiler ignores the project setting in generated code, so the directive is required there. - `tests/Opc.Ua.Interop.LegacyPeer` keeps `disable`. It builds against the 1.5.378 packages and does not import the repository props. - `docs/DeveloperGuide.md` and `.github/copilot-instructions.md` now describe the central setting. **Migration** (the 43 unset and 13 annotations-only projects, roughly 10k diagnostics) - **Production code: annotated at the declarations.** Covers `tools/Opc.Ua.SourceGeneration.Core`, `src/Opc.Ua.Lds.Server` and the LDS sample. - Members, parameters and returns are nullable where null actually flows. - Null-check helpers carry `[NotNullWhen]` (`IsNull`, `IsValidXmlName`, `IsMethodTypeDesign`). - `CoreUtils.Format`, `Utils.Format`, `ITemplateWriter.WriteLine` take `params object?[]`. `Template.AddReplacement` takes `string?` (rendering already skips null replacements). - `IRegisteredServerStore.RegisterAsync` returns `Task<RegistrationEntry?>`: it returns null when a registration is removed. - Dereferences backed by an invariant (for example, the validated model) use `!`, so runtime behaviour is unchanged. - **Behavioural changes in production code** (small, deliberate): - `NodeSetToModelDesign` throws `InvalidOperationException` when the file is not a NodeSet. Before, it threw a NullReferenceException on the next line. - `ModelDesignValidator` starts its lookup tables empty instead of null. A redundant `if (m_nodes == null) return;` guard is gone. - **Test code: minimal annotations.** - `?` on values that can be null, `!` where the test asserts non-null. - `= null!` for fixture members assigned in SetUp. - `TryGetStructure<T>(out T? x)` where the inferred `T?` broke the `IEncodeable` constraint. - A few redundant `== null` checks on structs (`NodeId`, `ArrayOf<T>`) folded into the adjacent `IsNull`/`Count` checks. **Notes for reviewers** - The diff is large (~1,500 files) but almost entirely mechanical: `?`, `!` and directive removal. The non-annotation changes are listed above. - Known follow-up, not done here: `IModelDesign.Namespaces/TargetNamespace/NamespaceUris` stay non-nullable even though the validator returns null before it loads a dictionary. Making them nullable would ripple through every generator call site. ## Related Issues - Fixes #4439 ## Checklist - [ ] 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. - [ ] 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. - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [ ] I have addressed **all** PR feedback received. The "no new warnings" box covers net10.0 and net48: full non-incremental builds of `UA.slnx`, `tools/SourceGeneration.slnx`, `tools/MigrationAnalyzer.slnx` and the Aot test projects finished with 0 warnings and 0 errors under the repository's warnings-as-errors settings. net9.0 and net8.0 also had no nullable diagnostics, but they were not rebuilt after the final fixes. Tests have not been run locally yet. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Visual Inspection Agent
VisualInspectionAgent is the external deterministic orchestrator for the
VisualInspectionCell server. It uses typed OPC UA
clients for Vision, AI Model Management, ISA-95 Job Control V2, and Alarms &
Conditions; it does not call server internals.
The model path never owns the production verdict. The agent captures a fixture
image, measures characteristics, routes the measurements through the AI
companion deployment's Invoke method for provenance, and then applies the
recipe rule locally before touching job control.
flowchart TD
Start["Start cycle"] --> Capture["Get fixture PNG from FixtureImages"]
Capture --> Measure["Measure BoreDiameter, SlotWidth, EdgeOffset"]
Measure --> Invoke["Call deployment Invoke for provenance"]
Invoke --> Judge["Apply deterministic recipe rule"]
Judge --> Decision{"Verdict"}
Decision -->|"Ok"| CompleteOk["Complete and close inspection job"]
CompleteOk --> NextInspection["StoreAndStart VIS-INSP-BRACKET-001"]
Decision -->|"NotOk"| CompleteBad["Complete and close inspection job"]
CompleteBad --> Rework["StoreAndStart VIS-REWORK-REJECT-001"]
Decision -->|"NotDecidable"| Hold["Hold for operator dialog"]
Hold --> Correction["Submit ground-truth correction"]
Running
Start the cell first:
dotnet run --project samples\Vision\VisualInspectionCell\VisualInspectionCell.csproj -- --insecure
Then run the agent:
dotnet run --project samples\Vision\VisualInspectionAgent\VisualInspectionAgent.csproj -- --server opc.tcp://localhost:62865/VisualInspectionCell --insecure --mode scripted --cycles 3
Options
| Option | Meaning |
|---|---|
--server <url> |
Server endpoint. Default opc.tcp://localhost:62865/VisualInspectionCell. |
--insecure |
Demo-only certificate convenience. |
--mode scripted|live-ai|human |
Selects the operator/model mode. Default scripted. |
--cycles <n> |
Number of fixture cycles. Default 3, minimum 1. |
--operator-timeout <seconds> |
Bounded human wait. Default 10, minimum 1. |
--ai-endpoint <uri> |
Required for live-ai. |
Exit codes
| Code | Meaning |
|---|---|
0 |
The requested cycles ran to completion. |
2 |
The command line was rejected, for example an unknown --mode or live-ai without --ai-endpoint. No session is opened and no job is created. |
3 |
The cell refused the run, for example an ISA-95 ReturnStatus that is not success, or a deployment that cannot serve live-ai. Reported as a single diagnostic line, not a stack trace. |
Mode names are matched without regard to case, hyphens or underscores, so
live-ai, live_ai and LiveAI are the same mode. An unrecognised mode is
rejected rather than quietly treated as scripted, because a silent downgrade
would run the simulated analyser while the operator believed a real model was
deciding.
Modes
scripted— the unattended path. The analyser and operator disposition are deterministic, and--cycles Nmakes the run finite.live-ai— requires--ai-endpoint. If no endpoint is configured, the agent exits before creating any ISA-95 job. There is no silent fallback to the simulated analyser, because a silently degraded sample appears to work while demonstrating nothing.human— waits for a real dialog subscriber for a bounded time. A timeout stops or holds; it never auto-approves and never blocks forever.
Job policy
Quality and job execution state are separate facts. A defective part does not mean the inspection job failed.
| Verdict | Inspection job | Next job |
|---|---|---|
Ok |
complete, close | schedule next inspection |
NotOk |
complete, close | schedule rework/reject order |
NotDecidable |
hold | none until the operator answers |
Scheduling chooses from the fixed catalogue exposed by the cell and calls ISA-95
V2 StoreAndStart. The agent never invents a job-order payload outside that
catalogue.
Operator feedback and learning
For NotDecidable, the agent responds to OperatorDispositionDialog and sends
Vision feedback with VisionFeedbackPurposeEnum.GroundTruthLabel. An accepted
positive correction carries the measured characteristics. An accepted negative
example uses the RetractAll path. The same correction is submitted twice in
the current orchestrator to demonstrate that the cell counts a learning sample
idempotently per stable sample id.
Related docs
- Vision developer guide — see Visual inspection: a cross-companion cell
- VisualInspectionCell
- Vision developer guide
- AI Model Management developer guide
- ISA-95 developer guide