fix(ab): share status map + libplctag orphan release, harden confinement guard, pin width-mismatch + DefaultTransport wiring (#568 #569 #580 #585)

- Ab.Transport: AbOpcUaStatusMap is the single AbStatus->OPC UA table; AbCip/AbLegacy mappers delegate.
- Remove dead MapCipGeneralStatus / MapPcccStatus / AbLegacy MapException (+ tests).
- LibplctagOrphanRelease is one source file (Ab.LibplctagShared) compiled into both drivers.
- LibplctagConfinementGuardTests: tokenising comment/literal stripper, block comments, csproj PackageReference guard.
- LibplctagStatusTranslationTests: real LibPlcTagException round trip, WrapSync/Wrap OCE pass-through, adapter routes decode via WrapSync.
- AbCipTypeMismatchTests: libplctag width-mismatch path (BadTypeMismatch, handle kept, no Degraded, warn once).
- Host: AddAbCipDriverDefaults extension + Program.cs source pin for Drivers:AbCip:DefaultTransport.
- docs/drivers/AbCip.md: layout rows + benign Degraded-after-outage note.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-07 22:59:06 -04:00
parent 04b6563286
commit 341417f193
25 changed files with 430 additions and 310 deletions
@@ -32,8 +32,7 @@ public sealed class AbCipDriverDefaultsOptionsTests
var services = new ServiceCollection();
services.AddLogging();
services.AddSingleton<ISecretResolver>(new StubSecretResolver());
services.AddValidatedOptions<AbCipDriverDefaultsOptions, AbCipDriverDefaultsOptionsValidator>(
configuration, AbCipDriverDefaultsOptions.SectionName);
services.AddAbCipDriverDefaults(configuration);
services.AddOtOpcUaDriverFactories();
return services.BuildServiceProvider();
}
@@ -48,6 +47,27 @@ public sealed class AbCipDriverDefaultsOptionsTests
private static AbCipDriver Create(ServiceProvider sp, string json) =>
(AbCipDriver)sp.GetRequiredService<DriverFactoryRegistry>().TryGet(DriverTypeNames.AbCip)!("drv-i1", json);
/// <summary>
/// #585 composition-root pin: <c>Program.cs</c> is top-level statements and cannot be booted in a unit test, so
/// the registration lives in <see cref="AbCipDriverDefaultsRegistration.AddAbCipDriverDefaults"/> (which every
/// test here builds through) and this asserts Program.cs still CALLS it. Deleting the call silently ignores an
/// operator's <c>Drivers:AbCip:DefaultTransport</c> pin — every behaviour test above would stay green.
/// </summary>
[Fact]
public void Program_registers_the_node_default_transport()
{
var dir = new DirectoryInfo(AppContext.BaseDirectory);
while (dir is not null && !File.Exists(Path.Combine(dir.FullName, "ZB.MOM.WW.OtOpcUa.slnx"))) dir = dir.Parent;
dir.ShouldNotBeNull();
var program = File.ReadAllText(Path.Combine(dir.FullName, "src", "Server", "ZB.MOM.WW.OtOpcUa.Host", "Program.cs"));
System.Text.RegularExpressions.Regex.IsMatch(
program, @"^builder\.Services\s*\.\s*AddAbCipDriverDefaults\(\s*builder\.Configuration\s*\)\s*;",
System.Text.RegularExpressions.RegexOptions.Multiline)
.ShouldBeTrue("Program.cs must call builder.Services.AddAbCipDriverDefaults(builder.Configuration), unconditionally " +
"(at top level, not inside an if (hasDriver/hasAdmin) block — the probe on admin nodes needs it too)");
}
[Fact]
public void Absent_section_defaults_to_Native()
{