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 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 N makes 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.