Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
158 commits
Select commit Hold shift + click to select a range
7341e60
FAT: add canonical legacy evidence migration
masarray Sep 11, 2026
7560f65
FAT: enforce canonical static rows before production host
masarray Sep 11, 2026
e8254db
FAT: regress canonical legacy row migration
masarray Sep 11, 2026
2cc9495
FAT: keep legacy selection cleanup as canonical guard
masarray Sep 11, 2026
f3657cd
Fix field FAT canonical authority normalization
masarray Sep 11, 2026
169c530
Add regression for Engineering authority field failure
masarray Sep 11, 2026
fd65abe
Keep Engineering FAT authority immutable during migration
masarray Sep 11, 2026
cefe09e
Recognize Engineering projection as static DataSet authority
masarray Sep 11, 2026
5ba59bd
Assert immutable Engineering DataSet authority field contract
masarray Sep 11, 2026
4a02d82
Fix required fields in Engineering authority regression fixture
masarray Sep 11, 2026
eac060b
FAT P1A: stop legacy bootstrap on Engineering tab entry
masarray Sep 11, 2026
f6ca0bf
Test P1A Engineering to FAT navigation boundary
masarray Sep 11, 2026
4f463bb
Add P1B sparse canonical FAT evidence overlay
masarray Sep 11, 2026
1a3ba25
Finish P1A native FAT grid and wire P1B evidence overlay
masarray Sep 11, 2026
52e8fce
Complete P1A direct Engineering FAT surface
masarray Sep 11, 2026
0258c09
Test P1B sparse evidence on canonical Engineering row keys
masarray Sep 11, 2026
7a3323b
Lock P1A direct rows and P1B overlay boundaries
masarray Sep 11, 2026
1da860a
Add sparse native FAT overlay state
masarray Sep 11, 2026
ea3117b
Preserve M6 FAT host authority contract
masarray Sep 11, 2026
9ade43b
Harden P1 canonical grid regression semantics
masarray Sep 11, 2026
76baae7
P1C align native FAT grid with Engineering visual authority
masarray Sep 11, 2026
467c498
Add P1C shared Engineering grid visual regression coverage
masarray Sep 11, 2026
22afb4b
Add P1D native FAT arm session state
masarray Sep 11, 2026
113eb45
Add P1D ARM-only native FAT coordinator
masarray Sep 11, 2026
4dca6aa
Make P1D sparse FAT evidence access thread-safe
masarray Sep 11, 2026
eb73dbc
Implement P1D ARM-only Engineering FAT start
masarray Sep 11, 2026
13f9230
Dispose P1D native FAT arm subscriptions on shutdown
masarray Sep 11, 2026
d90e426
Add P1D ARM-only FAT regression coverage
masarray Sep 11, 2026
ab45352
Fix P1D rolling evidence transition semantics
masarray Sep 11, 2026
2b88ce6
Add P2 native FAT evidence hydration state
masarray Sep 12, 2026
926d7a8
Add P2 snapshot and merge helpers for sparse evidence
masarray Sep 12, 2026
003b435
Add P2 async native FAT evidence hydration
masarray Sep 12, 2026
99c226d
Implement P2 async evidence hydration and shared rolling clock
masarray Sep 12, 2026
e1c96ad
Add P2 async evidence hydration regression coverage
masarray Sep 12, 2026
c363caf
Fix P2 rolling dots switch expression
masarray Sep 12, 2026
bb0bf77
Hydrate P2 from existing FAT snapshots without bootstrap
masarray Sep 12, 2026
495fde7
Test P2 passive legacy evidence hydration
masarray Sep 12, 2026
f877d9c
FAT P3: add immutable native preview snapshot
masarray Sep 12, 2026
124e54a
FAT P3: add lazy native print preview renderer
masarray Sep 12, 2026
680631a
FAT P3: wire lazy immutable preview capture
masarray Sep 12, 2026
f1c74c4
FAT P3: expose lazy selected-IED print preview
masarray Sep 12, 2026
723e986
FAT P3: add lazy immutable preview regressions
masarray Sep 12, 2026
c5e3ec0
FAT P4A: bind evidence by IEDName and IEC telegram
masarray Sep 12, 2026
28a55fb
FAT P4B: preserve timestamped structured evidence
masarray Sep 12, 2026
5ef1b9f
FAT P4C: add canonical seven-column contract
masarray Sep 12, 2026
1a1ae47
FAT P4C: apply seven-column Explorer view
masarray Sep 12, 2026
4515916
FAT P4C: lock canonical seven-column regression
masarray Sep 12, 2026
8d6045c
FAT P4C: fix evidence field namespace
masarray Sep 12, 2026
df7353e
FAT: remove duplicate P4C column application
masarray Sep 12, 2026
6dba347
FAT: make P4C columns canonical grid authority
masarray Sep 12, 2026
625b5b9
test: lock P4C single-authority column contract
masarray Sep 12, 2026
f3a8b13
FAT P4D: align immutable preview snapshot with P4C columns
masarray Sep 12, 2026
dde92be
FAT P4D: add immutable snapshot report layout adapter
masarray Sep 12, 2026
d4eb066
FAT P4D: render immutable preview through FixedDocument viewer
masarray Sep 12, 2026
ab249ba
test: keep P3 immutable snapshot contract under P4D
masarray Sep 12, 2026
6964efe
test: lock P4D FixedDocument preview contract
masarray Sep 12, 2026
6b5d4c9
test: align P1 visual contract with P4C Live Value authority
masarray Sep 12, 2026
9ebe0a9
FAT P4D: preserve timestamped evidence in report snapshot
masarray Sep 12, 2026
dcec536
FAT P4D: add layout-first native PDF serialization
masarray Sep 12, 2026
47df5d4
FAT P4D: save immutable layout through shared PDF service
masarray Sep 12, 2026
f4edbe0
FAT P4D: add Save PDF parity to FixedDocument preview
masarray Sep 12, 2026
8b814dd
test: align immutable preview with timestamped P4B evidence
masarray Sep 12, 2026
b13a89b
test: close P4D preview and PDF parity contract
masarray Sep 12, 2026
f9d248e
test: start P4E evidence isolation regression gauntlet
masarray Sep 12, 2026
220285c
FAT P4E: add exact command feedback correlation
masarray Sep 12, 2026
712575c
test: lock P4E command feedback identity correlation
masarray Sep 12, 2026
9ec392d
test: make P4D PDF source contract line-ending agnostic
masarray Sep 12, 2026
af9c572
test: align P1 sparse evidence contract with P4C column authority
masarray Sep 12, 2026
1a8d358
refactor: start P5 by isolating manual FAT compatibility host
masarray Sep 12, 2026
8fc44d8
refactor: remove dead automatic Engineering FAT bootstrap module
masarray Sep 12, 2026
e875d5e
test: lock P5 removal of automatic Engineering FAT bootstrap
masarray Sep 12, 2026
143fca9
refactor: remove legacy bootstrap state parameters from native FAT host
masarray Sep 12, 2026
8f1bf20
fix: require explicit control feedback reference for stable confirmation
masarray Sep 12, 2026
e8e92f1
test: lock explicit-only command feedback identity
masarray Sep 12, 2026
83d4976
test: lock P5 native FAT runtime against legacy bridge reentry
masarray Sep 12, 2026
3de255e
FAT P5: remove dead Engineering projection bridge
masarray Sep 12, 2026
7bfec6c
test: align Engineering FAT regression with P5 bridge removal
masarray Sep 12, 2026
3d59731
test: retire P0 bootstrap-only regressions after P5
masarray Sep 12, 2026
b1e4600
test: make M7 cleanup enforce P5 bootstrap retirement
masarray Sep 12, 2026
2619d2c
test: make permanent-host regression follow P5 native entry
masarray Sep 12, 2026
24cffc1
test: close P5 legacy bridge removal gate
masarray Sep 12, 2026
3375654
FAT P5: retire zero-reparse Engineering workspace adoption bridge
masarray Sep 12, 2026
2040ba9
test: lock retired Engineering workspace adoption bridge
masarray Sep 12, 2026
faad2ff
FAT P5: refresh canonical FAT after static authority update
masarray Sep 12, 2026
367db09
fix: preserve shared SCL workspace while removing bootstrap caller
masarray Sep 12, 2026
6ea69c7
test: make P4E feedback fallback guard comment-safe
masarray Sep 12, 2026
a6e97d5
fix: separate native FAT values from evidence timestamps
masarray Sep 12, 2026
26f040a
fix: split FAT values timestamps and complete result
masarray Sep 12, 2026
4c5765a
fix: snapshot exact FAT signal and timestamp columns
masarray Sep 12, 2026
34b0d32
fix(fat): align report with nine-column evidence grid
masarray Sep 12, 2026
1038e53
fix(fat): restore native report signoff page
masarray Sep 12, 2026
f905dfd
fix(fat): finalize native report with signoff
masarray Sep 12, 2026
d7801ad
fix(fat): restore professional native print preview
masarray Sep 12, 2026
bc86619
test(fat): lock professional preview and nine-column report
masarray Sep 12, 2026
3328509
fix(fat): refresh timestamp evidence with value cells
masarray Sep 12, 2026
4fe1fa4
fix(fat): refresh timestamp columns through evidence contract
masarray Sep 12, 2026
bd7ff38
test(fat): lock live timestamp refresh in nine-column grid
masarray Sep 12, 2026
c94e4c9
fix(fat): surface observation progress after evidence capture
masarray Sep 12, 2026
0288bd2
test(fat): lock one-of-two and two-of-two observation feedback
masarray Sep 12, 2026
dd2ecfa
fix(fat): restore print snapshot evidence namespace
masarray Sep 12, 2026
09dbcd9
fix(fat): unify preview timestamp contract
masarray Sep 12, 2026
dd793e6
fix(fat): disambiguate preview path types
masarray Sep 12, 2026
3fd2549
test(fat): lock stable identity against current key authority
masarray Sep 12, 2026
c07aee3
test(fat): align grid regression with nine-column contract
masarray Sep 12, 2026
d0f0e94
test(fat): assert structured timestamp columns separately
masarray Sep 12, 2026
cf4a63e
test(fat): keep evidence isolation with split timestamps
masarray Sep 12, 2026
27eee87
test(fat): assert immutable preview with split timestamps
masarray Sep 12, 2026
318c65b
test(fat): lock report preview to split timestamp evidence
masarray Sep 12, 2026
67c2d63
fix(control): default interlock and sync checks once per signal
masarray Sep 12, 2026
d49fa22
test(control): lock safe interlock and sync defaults
masarray Sep 12, 2026
e03c998
fix(fat): use shared operator signal names in report snapshots
masarray Sep 12, 2026
16e5c0e
feat(fat): add shared vector report branding
masarray Sep 12, 2026
1bdad19
fix(fat): clean customer report and map completed rows to OK
masarray Sep 12, 2026
65086f0
fix(fat): remove internal wording from sign-off report
masarray Sep 12, 2026
62c45e9
fix(fat): center preview controls and fit whole page
masarray Sep 12, 2026
0505208
test(fat): lock report cleanup and whole-page preview contract
masarray Sep 12, 2026
a15cba6
fix(fat): polish report preview presentation
masarray Sep 13, 2026
8669135
fix(fat): compile report logo image pipeline
masarray Sep 13, 2026
7021aa3
feat(fat): add native COMTRADE and time sync diagnostics
masarray Sep 13, 2026
a11fe41
test(fat): relax FileDirectory source guard
masarray Sep 13, 2026
2a60928
fix(fat): pin evidence to recycled rows and flush on shutdown
masarray Sep 13, 2026
b6dc2f6
test(fat): tighten recycled-row field guard
masarray Sep 13, 2026
9c407d9
fix(fat): bind evidence cells to canonical row identity
masarray Sep 13, 2026
0116c06
fix(fat): use DataContext-bound evidence columns
masarray Sep 13, 2026
0c50fbf
fix(fat): install bound evidence runtime and flush on close
masarray Sep 13, 2026
a0a4c4d
refactor(fat): remove row-recycle text patch
masarray Sep 13, 2026
0e12599
fix(report): fit logos adaptively in header slot
masarray Sep 13, 2026
201345a
test(fat): exercise real WPF row recycling and adaptive logos
masarray Sep 13, 2026
06d7b5e
test(fat): enable WPF recycling regression harness
masarray Sep 13, 2026
86b7a18
test(fat): expect DataContext-bound evidence columns
masarray Sep 13, 2026
fd246bc
test(fat): validate bound evidence refresh authority
masarray Sep 13, 2026
be32d1e
test(fat): import root ARSAS namespace for WPF harness
masarray Sep 13, 2026
6e5366e
fix(ci): expose internals to regression tests
masarray Sep 13, 2026
48e1710
fix(ci): restore test implicit IO namespace
masarray Sep 13, 2026
b678b14
fix(fat): freeze evidence before per-IED persistence
masarray Sep 13, 2026
601985d
fix(fat): persist capture before IED teardown
masarray Sep 13, 2026
f99790d
fix(fat): remove obsolete compatibility navigation button
masarray Sep 13, 2026
a7f428c
test(fat): cover teardown persistence and compatibility guard
masarray Sep 13, 2026
45789fc
fix(fat): keep persistence worker compile-safe
masarray Sep 13, 2026
4e10d81
fix(fat): import evidence session model for durability worker
masarray Sep 13, 2026
6b76648
perf(fat): reuse frozen IED identity and coalesce evidence writes
masarray Sep 13, 2026
4d42d18
perf(fat): freeze canonical IED identity once per runtime session
masarray Sep 13, 2026
36cfe1d
fix(fat): add IED-owned sparse evidence store
masarray Sep 13, 2026
f59cc25
refactor(fat): persist detached sparse evidence per IED
masarray Sep 13, 2026
083a93a
fix(fat): auto-load evidence by IEDName independent of FAT arm
masarray Sep 13, 2026
3203d2d
test(fat): cover IED-owned auto-load and continued capture
masarray Sep 13, 2026
3768ff5
perf(fat): load each IED evidence store once per runtime session
masarray Sep 13, 2026
0416a98
fix(fat): keep evidence snapshot valid after IED detach
masarray Sep 13, 2026
41bd8b1
fix(fat): persist capture even after rapid IED removal
masarray Sep 13, 2026
ba5c8e1
perf(fat): prepare evidence independently for every opened IED
masarray Sep 13, 2026
15c7692
fix(fat): defer SCL evidence load until IED identity is applied
masarray Sep 13, 2026
be0d867
fix(fat): remove obsolete workspace switcher
masarray Sep 13, 2026
e165ad2
feat(fat): include verified auxiliary evidence in reports
masarray Sep 13, 2026
7cded3e
FAT report: improve evidence table readability
masarray Sep 13, 2026
87888be
Tests: lock readable eight-column FAT report layout
masarray Sep 13, 2026
d51405a
Report: enlarge evidence text and compact table rows
masarray Sep 13, 2026
68d0daf
Report: harden customer wording and local timestamps
masarray Sep 13, 2026
08185c9
Tests: align report wording and local timestamp regressions
masarray Sep 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions IoListTestingWindow.ReleaseNavigationGuard.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Threading;

namespace ArIED61850Tester;

/// <summary>
/// Release guard for the obsolete compatibility navigation control. Native FAT now lives in
/// the Engineering workstation itself, so the old "Engineering" return button has no valid
/// user-facing purpose and can re-enter the compatibility mount/unmount path unexpectedly.
/// Keep the legacy host code available for explicit compatibility work, but remove its risky
/// navigation button from the shipped UI.
/// </summary>
public partial class IoListTestingWindow
{
private bool _releaseObsoleteEngineeringButtonHidden;

[ModuleInitializer]
internal static void RegisterIoListFatReleaseNavigationGuard()
{
EventManager.RegisterClassHandler(
typeof(IoListTestingWindow),
FrameworkElement.LoadedEvent,
new RoutedEventHandler(IoListFatReleaseNavigationGuard_Loaded),
handledEventsToo: true);
}

private static void IoListFatReleaseNavigationGuard_Loaded(object sender, RoutedEventArgs e)
{
if (sender is not IoListTestingWindow window || window._releaseObsoleteEngineeringButtonHidden)
return;

window._releaseObsoleteEngineeringButtonHidden = true;
window.Dispatcher.BeginInvoke(
new Action(() => HideObsoleteEngineeringNavigation(window)),
DispatcherPriority.Loaded);
}

private static void HideObsoleteEngineeringNavigation(DependencyObject root)
{
if (root is Button button &&
string.Equals(button.Content?.ToString()?.Trim(), "Engineering", StringComparison.OrdinalIgnoreCase))
{
button.IsEnabled = false;
button.Focusable = false;
button.Visibility = Visibility.Collapsed;
return;
}

var childCount = VisualTreeHelper.GetChildrenCount(root);
for (var index = 0; index < childCount; index++)
HideObsoleteEngineeringNavigation(VisualTreeHelper.GetChild(root, index));
}
}
20 changes: 20 additions & 0 deletions MainWindow.CommandPanelUx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public object ConvertBack(object value, Type targetType, object parameter, Cultu

private readonly ConditionalWeakTable<Button, Marker> _configuredCommandButtons = new();
private readonly ConditionalWeakTable<Button, TactileButtonState> _tactileButtonStates = new();
private readonly ConditionalWeakTable<SignalDefinition, Marker> _controlSafetyDefaultsApplied = new();
private readonly HashSet<string> _controlModelPreloadAttempts = new(StringComparer.OrdinalIgnoreCase);
private readonly SemaphoreSlim _controlModelPreloadGate = new(1, 1);

Expand Down Expand Up @@ -170,6 +171,8 @@ private void ConfigureCommandPanelButton(Button button)
if (button.DataContext is not SignalDefinition signal)
return;

EnsureDefaultControlSafetyChecks(signal);

var content = button.Content?.ToString()?.Trim() ?? string.Empty;
if (content.Equals("Technical details", StringComparison.OrdinalIgnoreCase) ||
content.Equals("Not available", StringComparison.OrdinalIgnoreCase))
Expand Down Expand Up @@ -212,6 +215,20 @@ private void ConfigureCommandPanelButton(Button button)
_configuredCommandButtons.Add(button, new Marker());
}

private void EnsureDefaultControlSafetyChecks(SignalDefinition signal)
{
ArgumentNullException.ThrowIfNull(signal);
_controlSafetyDefaultsApplied.GetValue(signal, current =>
{
// Safe command defaults are applied to the model once, not painted into XAML.
// After this first initialization the operator remains free to clear either flag;
// the periodic command-panel UX refresh must never force a user choice back on.
current.ControlInterlockCheck = true;
current.ControlSynchroCheck = true;
return new Marker();
});
}

private static bool IsCommandActionButton(string content)
=> content is "Open" or "Close" or "True" or "False" or "Raise" or "Lower" or "Set";

Expand Down Expand Up @@ -251,6 +268,9 @@ private async Task PreloadControlModelsAsync()
{
foreach (var device in Devices.Where(device => device.IsConnected && device.SelectedControlSignalCount > 0))
{
foreach (var signal in device.Signals.Where(signal => signal.IsSelected && signal.IsValidControlObject))
EnsureDefaultControlSafetyChecks(signal);

// One MMS association is serialized. Do not queue background ctlModel
// inspection while an operator command owns the session.
if (device.CommandSignals.Any(signal => signal.ControlCommandBusy))
Expand Down
11 changes: 7 additions & 4 deletions MainWindow.ControlDiagnostics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -475,10 +475,13 @@ private bool HasFreshStableEvidence(ActivePositionCommand state)

private static string ResolveControlFeedbackKey(SignalDefinition signal)
{
var reference = string.IsNullOrWhiteSpace(signal.ControlStatusReference)
? $"{signal.ObjectReference}.stVal"
: signal.ControlStatusReference;
return NormalizeReference(reference);
// P4E fail-closed contract: stable command confirmation is allowed only when live
// control discovery supplied an explicit status reference. Never infer .stVal from
// ObjectReference, SignalName, row order, or any runtime identifier.
if (string.IsNullOrWhiteSpace(signal.ControlStatusReference))
return string.Empty;

return NormalizeReference(signal.ControlStatusReference);
}

private async Task ExpirePositionCommandAsync(ActivePositionCommand state)
Expand Down
38 changes: 36 additions & 2 deletions MainWindow.FieldPresentationFix.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ internal static class MainWindowFieldPresentationFix
{
private const string IedTimestampHeader = "IED Timestamp";
private const string SignalHeader = "Signal";
internal static SolidColorBrush CommandTargetForegroundBrush { get; } = FrozenBrush(0x58, 0x6B, 0x82);
internal static SolidColorBrush CommandTargetBackgroundBrush { get; } = FrozenBrush(0xF4, 0xF7, 0xFB);
internal static SolidColorBrush CommandTargetBorderBrush { get; } = FrozenBrush(0xD6, 0xE0, 0xEC);

[ModuleInitializer]
internal static void Register()
Expand Down Expand Up @@ -148,12 +151,36 @@ private static void ApplySemanticSignalColumns(MainWindow window)

private static void ApplyDarkCommandHeaderContrast(MainWindow window)
{
if (window.FindName("CommandPanelExpander") is not Expander expander || expander.Header is not DependencyObject header)
if (window.FindName("CommandPanelExpander") is not Expander expander)
return;

ApplyDarkCommandHeaderContrast(expander);
}

internal static void ApplyDarkCommandHeaderContrast(Expander expander)
{
if (expander.Header is not DependencyObject header)
return;

expander.Foreground = Brushes.White;
var targetBadge = VisualDescendants<Border>(header)
.FirstOrDefault(border => Equals(border.Tag, "P0CommandTargetBadge"));
var targetTexts = targetBadge == null
? new HashSet<TextBlock>()
: VisualDescendants<TextBlock>(targetBadge).ToHashSet();

if (targetBadge != null)
{
targetBadge.Background = CommandTargetBackgroundBrush;
targetBadge.BorderBrush = CommandTargetBorderBrush;
}

foreach (var text in VisualDescendants<TextBlock>(header).Prepend(header as TextBlock).OfType<TextBlock>())
text.Foreground = Brushes.White;
{
text.Foreground = targetTexts.Contains(text)
? CommandTargetForegroundBrush
: Brushes.White;
}
}

/// <summary>
Expand Down Expand Up @@ -216,6 +243,13 @@ private static IEnumerable<T> VisualDescendants<T>(DependencyObject root) where
}
}

private static SolidColorBrush FrozenBrush(byte red, byte green, byte blue)
{
var brush = new SolidColorBrush(Color.FromRgb(red, green, blue));
brush.Freeze();
return brush;
}

private sealed class RoundedIedTimestampConverter : IValueConverter
{
internal static readonly RoundedIedTimestampConverter Instance = new();
Expand Down
Loading
Loading