- Driver.AbCip-007: inject an optional ILogger<AbCipDriver> /
ILogger<AbCipAlarmProjection> (default NullLogger) and log around
every read / write / template-fetch / probe / alarm-poll failure path.
- Driver.AbCip-011: LogWarning when InitializeAsync is configured with
Probe.Enabled=true but ProbeTagPath is blank — operators now see why
GetHostStatuses keeps reporting Unknown.
- Driver.AbCip-012: documented the LibplctagTemplateReader per-call
Tag cost as accepted given libplctag's own connection pool and the
low-frequency discovery use-case.
- Driver.AbCip-013: per-device AllowPacking + ConnectionSize overrides
on AbCipDeviceOptions, threaded through AbCipTagCreateParams; central
BuildCreateParams helper replaces five ad-hoc clones; AllowPacking
now reaches Tag.AllowPacking at runtime.
- Driver.AbCip-015: stale-comment sweep — every PR-N forward-reference
is rewritten to describe present behaviour.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Clarify Driver.AbCip-005 resolution: parent Structure tag stays in
_tagsByName (needed by whole-UDT planner + alarm projection); the fix
is in ReadSingleAsync returning BadNotSupported for direct reads.
Update Driver.AbCip-014 resolution text to match the actual test names.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add regression tests for the Medium findings resolved in this series:
- AbCipDataType_maps_large_integer_types (theory: LInt→Int64, ULInt→UInt64,
UDInt→UInt32) and Read_UDInt_tag_returns_uint_value_not_negative_wrapped_int
cover Driver.AbCip-004.
- Structure_parent_tag_is_not_readable_after_member_fan_out,
InitializeAsync_throws_on_duplicate_tag_name, and
InitializeAsync_throws_when_member_name_collides_with_independent_tag
cover Driver.AbCip-005.
- Read_failure_evicts_runtime_so_next_read_creates_fresh_handle covers
Driver.AbCip-010.
AbCipDriverTests.AbCipDataType_maps_atomics_to_driver_types extended with
LInt/ULInt/UDInt assertions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add `EvictRuntime` helper that removes + disposes a stale
`ConcurrentDictionary` entry. Call it from `ReadSingleAsync`,
`ReadGroupAsync`, and `WriteAsync` on non-zero libplctag status and
transport exceptions so the next call for the same tag re-creates a
fresh handle — mirroring the probe loop's recreate-on-failure pattern.
Value-conversion exceptions (NotSupportedException, FormatException,
InvalidCastException, OverflowException) are not transport faults and
do not evict the handle.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The ConcurrentDictionary + TryAdd/dispose-loser pattern for Runtimes
and ParentRuntimes was already applied as part of the Driver.AbCip-008
fix. Recording resolution with evidence rather than applying a
duplicate change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`PlcTagHandle` and `DeviceState.TagHandles` were dead scaffolding: the
`ReleaseHandle` no-op never called `plc_tag_destroy` and the dict was
never populated. Removed the file, the dead dict, and its
`DisposeHandles` loop. Updated the `AbCipDriver` class doc to document
that native lifetime is owned by libplctag.NET `Tag.Dispose()` (invoked
from `DisposeHandles`) with the library's own finalizer covering any
GC-collected instances. Two test methods that only exercised the dead
`PlcTagHandle` class removed from `AbCipDriverTests`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Structure tags with declared Members no longer register the bare parent
name in `_tagsByName` — reading it would return Good/null, which is
misleading. Clients read individual member paths. Both the member
fan-out and the scalar-tag paths now perform a duplicate-key check that
throws `InvalidOperationException` naming both colliding entries (fail-
fast, consistent with the AbCipHostAddress validation pattern).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`ToDriverDataType` mapped LInt/ULInt to Int32 (truncation) and UDInt
to Int32 (negative wrap for values > Int32.MaxValue). DriverDataType
already carries Int64/UInt64/UInt32, so map each Logix 64-bit and
unsigned-32-bit type to the correct member. `DecodeValueAt` in
`LibplctagTagRuntime` updated to return uint/ulong for UDInt/ULInt
so the runtime value type agrees with the declared OPC UA type.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Driver.AbCip-001 — ReinitializeAsync silently discarded its config JSON.
Extracted AbCipDriverFactoryExtensions.ParseOptions; InitializeAsync now
re-parses a content-bearing driverConfigJson and replaces _options (and
recreates the alarm projection), so a reinitialize with a changed config
(new device/tag, changed timeout) actually takes effect. A blank or
empty-object JSON keeps construction-time options for the unit-test seam.
Driver.AbCip-002 — libplctag status mapping used wrong integer constants.
MapLibplctagStatus now switches on the libplctag.NET Status enum members
(Ok/Pending/ErrorTimeout/ErrorNotFound/ErrorNotAllowed/ErrorOutOfBounds/…)
instead of hand-typed natives, so timeout/not-found/not-allowed/out-of-bounds
get their specific OPC UA codes instead of all collapsing to
BadCommunicationError. The int overload casts to Status to stay correct
against the wrapper's contiguous renumbering.
Driver.AbCip-003 — whole-UDT reads decoded members at declaration-order
offsets, which Studio 5000 does not guarantee. Added the opt-in
AbCipDriverOptions.EnableDeclarationOnlyUdtGrouping flag (default false);
AbCipUdtReadPlanner.Build forms no groups when it is off, so by default
every UDT member reads per-tag rather than at possibly-wrong offsets.
Driver.AbCip-008 — probe loops were fire-and-forget and ShutdownAsync raced
them. Each probe Task is stored on DeviceState.ProbeTask; ShutdownAsync now
cancels every CTS, awaits each probe Task (10s timeout), then disposes the
CTS and handles. DeviceState.Runtimes/ParentRuntimes are ConcurrentDictionary
and the Ensure*RuntimeAsync paths use TryAdd, disposing the losing concurrent
creator instead of leaking a native tag handle.
Adds AbCipDriverCodeReviewRegressionTests and updates existing AbCip tests
to the corrected status constants + opt-in grouping flag. AbCip driver +
test project build clean; all 244 AbCip tests pass. (The full-solution
build has pre-existing, unrelated Driver.Galaxy protobuf-generation errors
in this worktree.)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reviewed all 31 src/ production projects against the 10-category
checklist in REVIEW-PROCESS.md. Each module gets its own findings.md;
code-reviews/README.md is regenerated from them.
334 findings: 6 Critical, 46 High, 126 Medium, 156 Low.
Critical findings:
- Server-001: WriteNodeIdUnknown recurses unconditionally — a HistoryRead
on an unresolvable node crashes the process (remote DoS).
- Admin-001/002: app-wide auth bypass (RouteView not AuthorizeRouteView)
plus unauthenticated mutating routes.
- Core.Scripting-001: System.Environment reachable from operator scripts;
Environment.Exit() terminates the server.
- Core.AlarmHistorian-001: rowIds/events parallel-list desync on a corrupt
payload misapplies outcomes — silent alarm-event data loss.
- Driver.Galaxy-001: ReconnectSupervisor is built but never triggered, so
a transient gateway drop permanently kills the event stream.
All findings are Status=Open; resolution is tracked per REVIEW-PROCESS.md
section 4. Review only — no source code changed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>