Merge hw4-udtwrite (#592/#611)

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe

# Conflicts:
#	docs/drivers/AbCip.md
This commit is contained in:
Joseph Doherty
2026-10-09 04:32:03 -04:00
12 changed files with 970 additions and 25 deletions
@@ -985,4 +985,41 @@ public sealed class RawBrowseCommitMapperTests
refused.ShouldBe(expectRefused);
blobRejected.ShouldBe(expectRefused);
}
// ---- AB CIP discovered UDT members (Gitea #611) ----------------------------------------------
/// <summary>
/// Gitea #611 — a controller-discovered UDT member leaf commits as an ordinary raw tag: its member path
/// is the <c>tagPath</c>, the template's atomic type is the <c>dataType</c>, and it lands READ-ONLY with
/// no <c>writable</c> key. That is the whole writability rule for discovered members: the driver writes a
/// member only once the operator authors the raw tag <c>ReadWrite</c> (the node-manager gate), and the
/// commit never pre-authors that — the browse's <c>SecurityClass</c> is not consulted.
/// </summary>
/// <param name="memberPath">The discovered member's full name (its Logix member path).</param>
/// <param name="browseName">The member's browse name.</param>
/// <param name="nativeType">The atomic type the template stated.</param>
[Theory]
[InlineData("_EmptyTemperatureSegment.RampTime", "RampTime", "Real")]
[InlineData("_EmptyTemperatureSegment.Enable", "Enable", "Bool")]
[InlineData("Program:Main.Motor1.Status.Code", "Code", "Int")]
public void A_discovered_AbCip_UDT_member_commits_read_only_with_its_member_path_and_type(
string memberPath, string browseName, string nativeType)
{
var row = RawBrowseCommitMapper.MapLeaf(
driverType: DriverTypeNames.AbCip,
fullName: memberPath,
browseName: browseName,
driverDataType: null,
defaultDataType: "Int32",
groupPrefix: null,
folderPath: null,
createGroups: false,
addressFields: NativeTypeFields(nativeType));
row.Tag.AccessLevel.ShouldBe(TagAccessLevel.Read);
var o = JsonNode.Parse(row.Tag.TagConfig)!.AsObject();
o["tagPath"]!.GetValue<string>().ShouldBe(memberPath);
o["dataType"]!.GetValue<string>().ShouldBe(nativeType);
o.ContainsKey("writable").ShouldBeFalse();
}
}