fix(historian-gateway): historize under the historian name, not the mux ref, when HistorianTagname overrides (FU-3)
The continuous-historization recorder conflated two identifiers into one string: the dependency mux fans DependencyValueChanged keyed by the driver FullName (the mux ref), but a value must be historized under the resolved historian name (HistorianTagname override, else FullName). In the common no-override case the two are equal, so it worked; with an override they diverge and the recorder registered mux interest under a key the mux never fans — that tag's values were never captured (and, had they been, would have been written under the mux ref). Carry BOTH identifiers through the seam: a new HistorizedTagRef(MuxRef, HistorianName) record on IHistorizedTagSubscriptionSink. The applier resolves MuxRef = FullName and HistorianName = override-or-FullName. The recorder now keeps a muxRef->historianName map: it registers/filters mux interest by MuxRef but writes the outbox entry (and drains) under HistorianName. The convergence handler re-registers the mux only when the registered key-set changes, so an override-only rename (same FullName) updates the write target without mux churn. Tests: a divergent-override recorder test (interest by mux ref, value written under the override name, never the mux ref) + an override-rename no-churn test; the applier feed tests now assert the full (mux ref, historian name) pairs. Runtime 348/0, OpcUaServer 327/0; 0 warnings. Closes FU-3. Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
This commit is contained in:
+20
-8
@@ -147,17 +147,17 @@ public sealed class AddressSpaceApplierProvisioningTests
|
||||
/// ref deltas the applier feeds it. A <see cref="Throw"/> flag simulates a faulting feed.</summary>
|
||||
private sealed class CapturingSubscriptionSink : IHistorizedTagSubscriptionSink
|
||||
{
|
||||
/// <summary>Refs the applier fed as ADDED.</summary>
|
||||
public List<string> Added { get; } = new();
|
||||
/// <summary>Ref pairs the applier fed as ADDED (mux ref + historian name).</summary>
|
||||
public List<HistorizedTagRef> Added { get; } = new();
|
||||
|
||||
/// <summary>Refs the applier fed as REMOVED.</summary>
|
||||
public List<string> Removed { get; } = new();
|
||||
/// <summary>Ref pairs the applier fed as REMOVED.</summary>
|
||||
public List<HistorizedTagRef> Removed { get; } = new();
|
||||
|
||||
/// <summary>When true, <see cref="UpdateHistorizedRefs"/> throws synchronously.</summary>
|
||||
public bool Throw { get; init; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public void UpdateHistorizedRefs(IReadOnlyList<string> added, IReadOnlyList<string> removed)
|
||||
public void UpdateHistorizedRefs(IReadOnlyList<HistorizedTagRef> added, IReadOnlyList<HistorizedTagRef> removed)
|
||||
{
|
||||
if (Throw) throw new InvalidOperationException("boom");
|
||||
Added.AddRange(added);
|
||||
@@ -182,7 +182,13 @@ public sealed class AddressSpaceApplierProvisioningTests
|
||||
|
||||
applier.Apply(plan);
|
||||
|
||||
sink.Added.ShouldBe(new[] { "Pump1.Temp", "40001" }, ignoreOrder: true); // override + FullName fallback
|
||||
// Each ref carries BOTH identifiers: the override tag feeds (mux ref = FullName "ref", historian
|
||||
// name = override "Pump1.Temp"); the no-override tag feeds (FullName "40001" as both).
|
||||
sink.Added.ShouldBe(new[]
|
||||
{
|
||||
new HistorizedTagRef("ref", "Pump1.Temp"),
|
||||
new HistorizedTagRef("40001", "40001"),
|
||||
}, ignoreOrder: true);
|
||||
sink.Removed.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
@@ -218,8 +224,14 @@ public sealed class AddressSpaceApplierProvisioningTests
|
||||
|
||||
applier.Apply(plan);
|
||||
|
||||
sink.Added.ShouldBe(new[] { "Pump1.B" }, ignoreOrder: true);
|
||||
sink.Removed.ShouldBe(new[] { "Pump1.Old", "Pump1.A" }, ignoreOrder: true);
|
||||
// All three tags default FullName "ref" (the mux ref); the override rename changes only the
|
||||
// historian name, so the changed tag feeds removed (ref, Pump1.A) + added (ref, Pump1.B).
|
||||
sink.Added.ShouldBe(new[] { new HistorizedTagRef("ref", "Pump1.B") }, ignoreOrder: true);
|
||||
sink.Removed.ShouldBe(new[]
|
||||
{
|
||||
new HistorizedTagRef("ref", "Pump1.Old"),
|
||||
new HistorizedTagRef("ref", "Pump1.A"),
|
||||
}, ignoreOrder: true);
|
||||
}
|
||||
|
||||
/// <summary>A synchronously-throwing subscription sink must NOT block or break the publish — the
|
||||
|
||||
Reference in New Issue
Block a user