Files
romanett 9b034f8291 Enable nullable reference types centrally; migrate all unset and annotations-only projects (#4700)
# 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>
2026-10-08 08:48:13 +02:00
..

Visual Inspection Cell

VisualInspectionCell is the server half of the visual-inspection sample. It hosts one address space containing four companion areas:

  • Vision — BracketFixtureCamera, the FixtureImages inline PNG clip endpoint, and BracketInspectionPipeline.
  • AI Model Management — the visual-inspection-primary deployment, a learning job, and the Invoke path used for model provenance.
  • ISA-95 Job Control V2 — an allowlisted inspection order and a rework/reject order.
  • Alarms & Conditions — OperatorDispositionDialog, a DialogConditionType used 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, and fixture_table as the frame id.
  • FixtureImages, an inline PNG clip endpoint with endpoint URI opcua-inline://visual-inspection-cell/fixtures.
  • BracketInspectionPipeline, bound to the camera, an AI deployment, a learning job, VisualInspectionInferenceProvider, and VisualInspectionFeedbackSink.
  • A fixed ISA-95 V2 catalogue with VIS-INSP-BRACKET-001 and VIS-REWORK-REJECT-001. InspectionJobControlProvider rejects any other job order id instead of accepting invented payloads.
  • OperatorDispositionDialog, the human-disposition condition the agent uses when the deterministic rule returns NotDecidable.

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.