# 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 Cell
VisualInspectionCell is the server half of the visual-inspection sample. It
hosts one address space containing four companion areas:
- Vision —
BracketFixtureCamera, theFixtureImagesinline PNG clip endpoint, andBracketInspectionPipeline. - AI Model Management — the
visual-inspection-primarydeployment, a learning job, and theInvokepath used for model provenance. - ISA-95 Job Control V2 — an allowlisted inspection order and a rework/reject order.
- Alarms & Conditions —
OperatorDispositionDialog, aDialogConditionTypeused when a result is not decidable.
The server is intentionally a host for companion composition, not the owner of the production decision loop. The paired VisualInspectionAgent connects as an external orchestrator and drives the cycle with typed clients.
graph TD
Server["VisualInspectionCell server"]
Vision["Vision root"]
Sensor["BracketFixtureCamera<br/>ImageSensorType"]
Clip["FixtureImages<br/>inline PNG clips"]
Pipeline["BracketInspectionPipeline<br/>InferencePipelineType"]
AI["AI Model Management<br/>deployment + learning job"]
Jobs["ISA-95 Job Control V2<br/>fixed order catalogue"]
Dialog["OperatorDispositionDialog<br/>DialogConditionType"]
Server --> Vision
Vision --> Sensor
Sensor --> Clip
Vision --> Pipeline
Pipeline -->|"Deployment"| AI
Pipeline -->|"LearningJob"| AI
Server --> Jobs
Server --> Dialog
Running
Prerequisites: .NET 10 SDK.
dotnet run --project samples\Vision\VisualInspectionCell\VisualInspectionCell.csproj -- --insecure
The default endpoint is opc.tcp://localhost:62865/VisualInspectionCell.
--host <name> and --port <number> configure the endpoint host and port. The
anonymous operator role is mapped for the local sample.
--insecure is a demo convenience. It accepts untrusted certificates and must
not be used for production systems.
It is also, in practice, required to run the pair on a fresh machine. The flag
sets AutoAcceptUntrustedCertificates on the server, and without it the cell
rejects the agent's self-signed certificate with BadCertificateUntrusted and
the agent fails with BadNotConnected - which reads like a connectivity problem
rather than a trust one. Start both halves with --insecure, or trust the
agent's certificate in the server's PKI store first.
Startup options
| Option | Meaning |
|---|---|
--host <name> |
Endpoint host name. Default localhost. |
--port <number> |
Endpoint port. Default 62865. |
--inferenceLocation OnServer|EdgeOffServer |
Selects the advertised Vision inference location. Default OnServer. |
--insecure |
Demo-only certificate convenience. |
Fixtures and measurement
The cell serves three 800 x 600 PNG fixtures from Fixtures/ through the
Vision media provider. The camera scale is 10 px/mm, so one pixel is 0.10 mm.
That one-pixel pitch is carried as VisionCharacteristicDataType.Uncertainty;
it is not invented to force a result.
| Fixture | Why it exists |
|---|---|
bracket-ok.png |
Bore diameter is 12.00 mm, so the interval [11.90, 12.10] is wholly inside [11.80, 12.20]. |
bracket-not-ok.png |
Bore diameter is 12.60 mm, so the interval [12.50, 12.70] is wholly outside the bore tolerance. |
bracket-ambiguous.png |
The intended slot is 8.15 mm, but at 10 px/mm it draws as 81.5 px and can only be represented as 8.10 mm. The interval [8.00, 8.20] straddles the 8.15 mm upper limit. |
What the cell publishes
BracketFixtureCamera(ImageSensorType) with simulated reality,RGB8, 800 x 600 resolution, 10 px/mm intrinsics, andfixture_tableas the frame id.FixtureImages, an inline PNG clip endpoint with endpoint URIopcua-inline://visual-inspection-cell/fixtures.BracketInspectionPipeline, bound to the camera, an AI deployment, a learning job,VisualInspectionInferenceProvider, andVisualInspectionFeedbackSink.- A fixed ISA-95 V2 catalogue with
VIS-INSP-BRACKET-001andVIS-REWORK-REJECT-001.InspectionJobControlProviderrejects any other job order id instead of accepting invented payloads. OperatorDispositionDialog, the human-disposition condition the agent uses when the deterministic rule returnsNotDecidable.
Deliberately not implemented
The sample publishes and counts learning samples, but it does not pretend to retrain a model or promote a candidate. That is the same line taken by the AI Model Management sample: a simulated MLOps loop would mislead readers about the part of the specification a sample cannot honestly demonstrate.
Related docs
- Vision developer guide — see Visual inspection: a cross-companion cell
- Vision developer guide
- AI Model Management developer guide
- ISA-95 developer guide
- Alarms and Conditions