From f183d1c148af85c955cf642f94aed3b8d95b089b Mon Sep 17 00:00:00 2001 From: masarray Date: Tue, 14 Jul 2026 16:49:11 +0700 Subject: [PATCH 01/24] chore: stage safe SCL workflow and layout repair --- .repair/apply.py | 354 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 354 insertions(+) create mode 100644 .repair/apply.py diff --git a/.repair/apply.py b/.repair/apply.py new file mode 100644 index 00000000..765ec70c --- /dev/null +++ b/.repair/apply.py @@ -0,0 +1,354 @@ +from pathlib import Path +import re + + +def read(path: str) -> str: + return Path(path).read_text(encoding="utf-8") + + +def write(path: str, text: str) -> None: + Path(path).write_text(text, encoding="utf-8", newline="\n") + + +def replace_once(text: str, old: str, new: str, label: str) -> str: + if new in text: + return text + count = text.count(old) + if count != 1: + raise SystemExit(f"{label}: expected exactly one source block, found {count}") + return text.replace(old, new, 1) + + +# --------------------------------------------------------------------------- +# Main window layout. Keep the P0 single Confirm button untouched. +# --------------------------------------------------------------------------- +xaml_path = "MainWindow.xaml" +xaml = read(xaml_path) + +xaml = replace_once( + xaml, + ''' + + + + + ''', + ''' + + + + ''', + "main row definitions") + +header_pattern = re.compile( + r'''\n \n''' + r''' .*?\n \n\n''' + r''' \n''' + r''' + + + + + + ''' +explorer_header_new = ''' + + + + + + + + + + + + + ''' +xaml = replace_once(xaml, explorer_header_old, explorer_header_new, "IED Explorer project actions") + +# Guard against the regression that caused the broken patch: P0 must still use one +# stable Confirm button bound to ControlPendingConfirmationLabel. +if xaml.count('Content="{Binding ControlPendingConfirmationLabel}"') != 1: + raise SystemExit("P0 regression guard: stable single Confirm button is missing") +if 'Content="Confirm Open"' in xaml or 'Content="Confirm Close"' in xaml: + raise SystemExit("P0 regression guard: legacy dual Confirm buttons detected") + +write(xaml_path, xaml) + + +# --------------------------------------------------------------------------- +# SCL workflow, smart dynamic reporting, quiet capability diagnostics, and +# one-time command panel expansion. +# --------------------------------------------------------------------------- +cs_path = "MainWindow.xaml.cs" +cs = read(cs_path) + +cs = replace_once( + cs, + ''' private bool _signalSelectionWizardOpen; + private bool _connectAllInProgress; +''', + ''' private bool _signalSelectionWizardOpen; + private bool _connectAllInProgress; + private readonly HashSet _autoExpandedCommandDevices = new(StringComparer.OrdinalIgnoreCase); +''', + "one-time command expansion state") + +cs = replace_once( + cs, + ''' Raise(nameof(ActiveIedTitle)); + Raise(nameof(ActiveIedSubtitle)); + RaiseWorkspaceCounts(); + // ctlModel inspection is preloaded independently of the Expander. Avoid +''', + ''' Raise(nameof(ActiveIedTitle)); + Raise(nameof(ActiveIedSubtitle)); + RaiseWorkspaceCounts(); + TryAutoExpandCommandPanelOnce(_selectedDevice); + // ctlModel inspection is preloaded independently of the Expander. Avoid +''', + "selected-device expansion hook") + +cs = replace_once( + cs, + ''' var status = $"{sourceName}: {document.Ieds.Count} IED/AP workspace(s), {offlineCount} offline model(s), {endpointCount} MMS endpoint(s) — {added} added, {refreshed} refreshed, {retained} active retained."; + SetStatus(status); + AddLog("INFO", "SCL", status); +''', + ''' var status = $"{sourceName}: {document.Ieds.Count} IED/AP workspace(s), {offlineCount} offline model(s), {endpointCount} MMS endpoint(s) — {added} added, {refreshed} refreshed, {retained} active retained."; + SetStatus(status); + AddLog("INFO", "SCL", status); + + if (firstImported != null && firstImported.Signals.Count > 0) + { + AddLog("INFO", "SCL", $"{firstImported.Name}: SCL model ready. Choose signals; saving the selection will continue to endpoint binding, connection, and monitoring."); + await OpenSignalSelectionWizardAsync(firstImported); + } +''', + "Open SCL selection workflow") + +cs = replace_once( + cs, + ''' device.AllowDynamicDataSetWrites = false; + device.SclWorkspace = workspace; +''', + ''' var allowDynamicReporting = ShouldAllowDynamicReportingForScl(signals); + device.AllowDynamicDataSetWrites = allowDynamicReporting; + device.SclWorkspace = workspace; +''', + "SCL dynamic reporting policy") + +cs = replace_once( + cs, + ''' device.Status = workspace.RequiresEndpointBinding ? "SCL model ready — bind endpoint" : "SCL model ready"; + device.Detail = workspace.RequiresEndpointBinding + ? "LD/LN/DO/DA are available offline. Press Play to bind an MMS endpoint; no discovery traffic was sent while opening the file." + : "LD/LN/DO/DA were loaded offline. Play performs a fast MMS association; Re-scan performs full design-versus-live verification."; + device.AcquisitionMode = "SCL offline design model"; +''', + ''' device.Status = workspace.RequiresEndpointBinding ? "SCL model ready — bind endpoint" : "SCL model ready"; + device.Detail = allowDynamicReporting + ? (workspace.RequiresEndpointBinding + ? "LD/LN/DO/DA are available offline. Static report coverage is incomplete; after signal selection and endpoint binding, ArIED will create an association-scoped dynamic DataSet and use a safe free RCB before polling fallback." + : "LD/LN/DO/DA were loaded offline. Static report coverage is incomplete; ArIED will use static coverage where available and create an association-scoped dynamic DataSet for uncovered selected signals before polling fallback.") + : (workspace.RequiresEndpointBinding + ? "LD/LN/DO/DA are available offline. Press Play to bind an MMS endpoint; no discovery traffic was sent while opening the file." + : "LD/LN/DO/DA were loaded offline. Play performs a fast MMS association; Re-scan performs full design-versus-live verification."); + device.AcquisitionMode = allowDynamicReporting + ? "SCL design • Smart Dynamic reporting prepared" + : "SCL offline design model"; +''', + "SCL workspace detail") + +log_method_pattern = re.compile( + r''' private void LogSclFindings\(string sourceName, IReadOnlyList findings\)\n \{.*?\n \}\n\n private bool EnsureSclEndpointBinding''', + re.S) +log_method_new = ''' private void LogSclFindings(string sourceName, IReadOnlyList findings) + { + var actionableFindings = findings + .Where(finding => !IsSmartDynamicCapabilityHint(finding)) + .ToArray(); + if (actionableFindings.Length == 0) + return; + + var groups = SclFindingAggregator.Group(actionableFindings); + if (groups.Count != actionableFindings.Length) + { + AddLog( + "INFO", + "SCL", + $"{sourceName} • grouped {actionableFindings.Length} actionable finding(s) into {groups.Count} diagnostic group(s). Full typed evidence remains attached to the SCL workspace."); + } + + foreach (var group in groups.Take(40)) + { + AddLog( + SclFindingAggregator.ToLogLevel(group.Severity), + "SCL", + $"{sourceName} • {group.Code} [{group.Scope}]: {group.ToDiagnosticMessage()}"); + } + + if (groups.Count > 40) + { + var omittedRawCount = groups + .Skip(40) + .Sum(group => group.Count); + AddLog( + "WARN", + "SCL", + $"{groups.Count - 40} additional diagnostic group(s), representing {omittedRawCount} actionable finding(s), were omitted from the live log."); + } + + if (groups.Any(group => SclFindingAggregator.IsBlockingSeverity(group.Severity))) + MarkDiagnosticAlert(); + } + + private static bool IsSmartDynamicCapabilityHint(SclWorkspaceFinding finding) + { + if (!finding.Severity.Equals("Warning", StringComparison.OrdinalIgnoreCase)) + return false; + + return finding.Code.Equals("SCL_REPORT_DATASET_UNASSIGNED", StringComparison.OrdinalIgnoreCase) || + finding.Code.Equals("SCL_REPORT_DATASET_UNRESOLVED", StringComparison.OrdinalIgnoreCase); + } + + private static bool ShouldAllowDynamicReportingForScl(IReadOnlyCollection signals) + { + return signals.Any(signal => + signal.CanPublishAsSignal && + (string.IsNullOrWhiteSpace(signal.DataSetReference) || + string.IsNullOrWhiteSpace(signal.ReportControlReference))); + } + + private void TryAutoExpandCommandPanelOnce(Iec61850MonitorDevice? device) + { + if (device == null || CommandPanelExpander == null || device.CommandSignals.Count == 0) + return; + if (!_autoExpandedCommandDevices.Add(device.DeviceId)) + return; + + Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() => + { + if (ReferenceEquals(SelectedDevice, device) && CommandPanelExpander != null) + CommandPanelExpander.IsExpanded = true; + })); + } + + private bool EnsureSclEndpointBinding''' +if "IsSmartDynamicCapabilityHint" not in cs: + cs, count = log_method_pattern.subn(log_method_new, cs, count=1) + if count != 1: + raise SystemExit(f"LogSclFindings replacement: expected one method, replaced {count}") + +cs = replace_once( + cs, + ''' device.IpAddress = wizard.RelayIpAddress; + device.Port = wizard.MmsPort; + device.Status = "SCL model ready"; + device.Detail = "Endpoint bound locally. Play will fast-connect from the SCL design model; Re-scan performs full comparison."; + device.RefreshComputed(); +''', + ''' device.IpAddress = wizard.RelayIpAddress; + device.Port = wizard.MmsPort; + device.Status = "SCL model ready"; + device.Detail = device.AllowDynamicDataSetWrites + ? "Endpoint bound locally. Saving the selected signals will connect and arm Smart Dynamic reporting with a safe free RCB before polling fallback." + : "Endpoint bound locally. Play will fast-connect from the SCL design model; Re-scan performs full comparison."; + device.RefreshComputed(); +''', + "endpoint binding detail") + +cs = replace_once( + cs, + ''' device.RefreshCommandSignalProjection(); + RebuildControlFeedbackIndex(device); + SetStatus($"{device.Name}: refreshed {candidates.Length} control value(s)."); +''', + ''' device.RefreshCommandSignalProjection(); + RebuildControlFeedbackIndex(device); + TryAutoExpandCommandPanelOnce(device); + SetStatus($"{device.Name}: refreshed {candidates.Length} control value(s)."); +''', + "command list ready expansion") + +cs = replace_once( + cs, + ''' AllowDynamicDataSetWrites = hasSclProvenance ? false : profile.AllowDynamicDataSetWrites, +''', + ''' AllowDynamicDataSetWrites = hasSclProvenance + ? ShouldAllowDynamicReportingForScl(cachedSignals) + : profile.AllowDynamicDataSetWrites, +''', + "restored SCL dynamic reporting policy") + +# P0 must remain present after all transformations. +required_p0_tokens = [ + "TryClaimControlConfirmation", + "ControlInspectionBusy", + "ControlCommandBusy", + "ExecuteClaimedControlAsync" +] +for token in required_p0_tokens: + if token not in cs: + raise SystemExit(f"P0 regression guard: missing {token}") + +write(cs_path, cs) + + +# --------------------------------------------------------------------------- +# Release alignment. +# --------------------------------------------------------------------------- +project_path = "ArIED61850Tester.csproj" +project = read(project_path) +project = project.replace("1.6.8", "1.6.9") +project = project.replace("1.6.8.0", "1.6.9.0") +project = project.replace("1.6.8.0", "1.6.9.0") +write(project_path, project) + +workflow_path = ".github/workflows/build.yml" +workflow = read(workflow_path).replace("-Version 1.6.8", "-Version 1.6.9") +write(workflow_path, workflow) + +publish_path = "scripts/publish-windows-portable.ps1" +publish = read(publish_path) +publish = publish.replace('[string]$Version = "1.6.8"', '[string]$Version = "1.6.9"') +publish = publish.replace("1.6.8 or v1.6.8", "1.6.9 or v1.6.9") +write(publish_path, publish) + +Path("docs/SCL_SMART_DYNAMIC_REPORTING.md").write_text( + """# SCL-assisted Smart Dynamic reporting\n\n" + "Opening an SCL file is an offline operation. After the model is loaded, ArIED opens the signal-selection workflow immediately. Saving a valid live selection continues to endpoint binding, MMS association, and monitoring.\n\n" + "When selected signals have complete static DataSet and ReportControl coverage, ArIED prefers that configuration. When coverage is missing or partial, ArIED prepares an association-scoped dynamic DataSet from the uncovered selected signals and asks the native engine to use a safe free RCB. MMS polling remains the final fallback.\n\n" + "Unassigned or unresolved ReportControl DataSet references reported as warnings are retained in the typed SCL workspace but are not promoted into noisy live Diagnostics because they are capability hints handled by Smart Dynamic reporting. A positively resolved empty DataSet and other blocking SCL findings remain actionable diagnostics.\n\n" + "The command panel expands automatically only once per IED, after the live control model has produced at least one proven executable command row.\n" + """, + encoding="utf-8") From 9567765a60f1fac4d3431635bf0fd49b53d99efa Mon Sep 17 00:00:00 2001 From: masarray Date: Tue, 14 Jul 2026 16:51:39 +0700 Subject: [PATCH 02/24] chore: stage supplied ico asset --- .repair/app-icon.b64 | 1 + 1 file changed, 1 insertion(+) create mode 100644 .repair/app-icon.b64 diff --git a/.repair/app-icon.b64 b/.repair/app-icon.b64 new file mode 100644 index 00000000..fb5d268c --- /dev/null +++ b/.repair/app-icon.b64 @@ -0,0 +1 @@ +AAABAAMAMDAAAAEAIACoJQAANgAAACAgAAABACAAqBAAAN4lAAAQEAAAAQAgAGgEAACGNgAAKAAAADAAAABgAAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wD///8A////AP///wD///8A////AP///wD///8A////AIgAAA+OBgAtjQMAXIwEAn6MBACJigQCiYkCAoqHAgKKgwICioECAoqAAAaKgAAHin4AC4p+AA+KegARinoAFop6AByKeAAfingCJYp4ACqKegAuinwANIp8AjeKfgA7in8APYl/Aj+JgABAfIEARluCAEkxeQBDEwAAAAH///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wCXAAAWkwcAbZMJAMZ+BwH2bQQB/WQDAf1gAwH9XgIC/VwCAv5bAQL+WQED/VgBA/5XAAT+VwEF/VQBB/5TAAj+UgEL/lIBDf1RARD+UgES/VIBFv1SARn9VAEc/VUBHv1XASH9WAEk/VkBJv1dASn9XgEs/WUBNPx0AUL4hQFP0YYCUYGHAFAgAAAAAf///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AqgAAA6UMAEGeDADMcwcB+1ECAf1HAAH9RAAB/UEAAf0/AAH9PQAB/T0AAf48AAH+OwEA/TsAAP47AAD+OgEA/jkBAP44AAH+NwAC/jcABP43AQb+NwEI/TgBCv05AQz9OgEO/jwBD/0+ARL9PgEU/UEBFf1DARf9RQAY/UcAGfxJARz9TwEh/WcBOPyGAVXeiwBZYYAAQAj///8A////AP///wD///8A////AP///wD///8A////AP///wC/AAAEtRIAYZYPAO5aBAH9RwAB/UIAAf0+AAH9OwAB/TkAAf04AAH9NwAB/TcBAf42AQH+NgEA/jcBAP43AAD+NgEA/jYAAf41AAL+NAAD/jQABf40AQj+NQAJ/TUBC/03AA39OAEP/ToAEf08ARP9PQEV/T4BFv1AARn+QgAZ/UUAGv1HARz9SgEd/U0BIP1VASf9egFN9o4CXoqAAE0K////AP///wD///8A////AP///wD///8A////AP///wDFGwBUnRQA8VEDAP1EAAH9PgAB/Ts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[... ELLIPSIZATION ...]//8A7t4ChOiZAPbfggD92ncA/dNtAP3KYQD9v1UA/LhNAP2wRQD9qDwA/aA1AP2ZLwD9lSsA/ZIoAP6PJQD+kCUA/ZkqAP2uOgL945M8/fzwQv375jj++95I/vvTY/77xob++720/e+Gyv24DXT9lQJU/YgBSv2DAUr9ggFM/YIBT/2FAlP9iAJY/YwCX/2QAmj9lgFx/ZwCev2iAoX9qAKP/a8CnP21Aqj9vwK3+twN47X///8A////AP///wD///8A7OwFNvC+AO/mkAD934IA/dp3AP3TbAD9yV8A/MJWAP25TQD9sEUA/ak8AP2iNAD9nC8A/ZcrAP6VKAD9lCcA/ZcoAv6iLQX9wkoO/fPMbf3763X+++WA/vvek/781Kz++cXO/dIoiP6kB1n9kgFM/okBS/2HAk39iAFQ/YgCVf2MAlv9jgJh/ZMCaf2XAnL9ngJ9/aQChv2qApL9rwKc/bYCqf2+Arb9zAfM8uIS6Ff///8A////AP///wD///8A29sAB/HeAMLtowD75o4A/eGCAP3bdwD90moA/MtgAP3DVgD9uU0A/bFEAP2pOwD9ozQA/Z4vAP6aKwD+mSkB/pknBP6eKAn9qy0Q/shJJP7cXTn+31xE/t9WTv7gT17+1TZn/bAQVf6aBk39kQJM/Y0BTf2NAlL9jgJX/Y8CXf2TAmT+lgJq/ZsCdP2fAn79pgGJ/asCk/2yAqD9twKr/cACuP3IAsj84xLo3tML3hf///8A////AP///wD///8A////AO3oADj0zADt7J8B/eePAP3iggD92XQA/dNqAP3MXwD9wVUA/bhLAP2xQgD9qjoA/aU0AP6hLwD+nisC/Z0oBv6dJgr9oyUR/aolGf6wJSH+syQo/rIfL/6wGjj+qhNB/Z8LR/2YBUn+kwJO/ZICU/2TAlj9lAFf/ZYCZv2aAm7+nQJ1/aICf/2nAov9rgKV/bQCof26Aq/9wQK6/ckCyv3ZDODz5BbsXv///wD///8A////AP///wD///8A////AP//AALv4wBu9MQB9e6fAP3ojgD94IAA/dp0AP3TaQD9yl0A/cFSAP25SgD9skIA/a06AP6oNAD+pC8C/aIrB/6iJwz9oyMS/aUgGv6mHSD+phsm/qQXLv2jEzX9oQ4+/ZwJRv2aBE39mAJT/ZgBWv2aAmL9nAJp/Z4Ccv2jAnz9pQKC/aoCjf2wApr9tgKl/bwCsf3DAsD9ygLO/dYJ3/nnGO+dxgDjCf///wD///8A////AP///wD///8A////AP///wDf3wAI8OQAevTIAPXuoQD95o0A/eGAAP3bdQD90mgA/cpcAP3DUwD9u0sA/bZCAP6xOwD+rDQD/qgvCf6nKg79pyUU/qcgHP6mHCP+pRkq/qMVMv6jETr9og1C/aAISv6eBFL9ngJb/Z8CZf2iAm79pQJ3/acCgf2tAoz9rwGT/bQCn/25Aqv9wAG3/cUBxP3LAtP92w/k+Ocf76LjHOMS////AP///wD///8A////AP///wD///8A////AP///wD///8A//8ABe3kAFT02ADW7q8A/OiRAP3iggD92nQA/dNpAP3MXgD9xVQA/b5KAP65QgD+tDsE/bE0Cv2uLg/9rSkW/a0jH/6sHif+qhsv/qgWN/2pEj/+qA5J/aYKU/2mBlz9pwJn/akCcv2sAn79rwKJ/bIClf24AqH9ugGo/cABtP3FAcL9zAHP/dUJ3vzhG+vm5SPtdNgU6w3///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDs4wAb8OAAdfLPAMXusQDy6JoA/eOLAP3efgD92HQA/dRqAP3QYgD9zVkD/cpSCP3ISw39xkMW/cY6Iv3FNSz9wi81/cEpP/3CJUr9wSBW/cEbYv3BF279whJ9/cMPjf3GDZz9yAys/coLufzNC8T90AvM/NcO2fzdFOL15R/qzugm7obqIuolqgCqA////wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AAAAAAe3bAA7x1QAk78YAP+y3AFLtqwBV6qIAVeqWAFXqkgBU54kAVOeDA1TneQZU5HAMVORnGFTkYSdU4Vg0VOFSQFTkTExU5EZbVOFAalThOnlU4TGSVOQup1TkJLlU5CHGVeQe1VXkHt5V4x/jUuQi6EPsJuwo4R7hEYAAgAL///8A////AP///wD///8A////AP///wD//wAD8OAAIfDEAFbrrwCC5psAl+KLAJjdfACY2HACmNVlB5jTWQ+Y0ksimNBBMpjOO0OY0DJUmM4qaJjOIoGY0hucmNUWtZjXFMiY2BTTl94X4IXhHOZc5SHrJ8wz/wX///8A////AP///wD///8A8AAAD+AAAAfAAAADgAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAHAAAAD4AAAA/AAAA8oAAAAEAAAACAAAAABACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAYAAAAx4BgBVZQMAtlwBANpYAQHeVQAC3lIAB95QAA7dUgAY3VUAIN1ZACfaYQAxuG8APVx2AE4NgAAAAoAAAAiPEgBwTQIA7ToAAfo2AAD8NAAA/DUAAPwzAAP9NQAJ/DkAEfw9ARj8QgEd/EYBIPpVASnweABKf3EAOQmoJAAjZgwA8TwBAfxHCQL9TgwE/U0JCP1JBhD9NAEJ/TcBE/1TBTv9XAZP/WAHWPxhB1b9UQI2/WIBN/WCAFstpDEAVEcDAfpEBgH8x10S/d9cEf3dSy392Dpb/WEOMP1eCkP91SDR/dsc4v3eJOj82D/k/XULfvxWAjf6gQBWX6M5A15RCAD6mUQO/Pd/Bf32bQr98mIl/fdWQ/3INWT9wiSS/fUZ3P3vHfD98h73/PQj+P3DOdL8YgRQ+4MAX2moQQNeWwwA+qVRFfz4iwL95GwQ/YQmDv3abDP99VlT/fQ4i/3mK8P9lxSX/cgh1fz0Hvj9zzva/WYFVfuGAGRprEgAX2EQAPtsGAP84owX/fWBBf2OMQ39kDsZ/fSBOP35VWf9wzeI/YwQdv3rIOv88DD1/ZQXmv1bAkD7hwBlaLRTAF9sFQH7XA4A/JxGC/3ylAj9124K/mYVBv3LdCr9+X9K/fNQf/3xJr799R/u/cQn0P1oAlX9WAE2+4kAZ2i8XgNfeh0A+2ERAP1zHwT92YoQ/fWSBP6KNAn9fywO/dR8P/3rWXz99ynC/eYs4/2IDH/9XQE8/VwBNvyRAm5ox24DX4wqAPtuFwD8aRUB/aJMD/32rwr+4YIN/VgOBf1nDw394kl5/fY1zP26KLb9bANO/WABOP1pAj/7mAJ9aM+BA1+iOwD7gSIA/XEYAP17HwH91YwX/viqA/2lUxL9njwr/fdXif3kRsv9hAVp/WcBP/1sAUH9egJP+6QCjmjZlgNfuE8A+5kzAP2DIgD9fB4B/aFGCf3yuQr976UP/u+FRP32ap79rB2K/XMDSP1xAUT9fQFS/Y0BafuxAqJo5LIAVtBrAPq1SwD8nDQA/YsmAP2OKQH92Zoc/fnIIP76qWD932ek/YsIV/15AUn9ggFW/ZIBbf2kAYr7xQW9YOvEACfllwDzz2kA/LdMAP2hNQD9lioB/a9EDv3rqU797ZmB/bIhbP2KAlD9iwFb/ZcBcv2pApD9wAO29tUK1THfnwAI7L4AeuOPAO/QaAD6uUkA+6g2AfylKg37rCQj/KsZO/udB0/7mgJj+6MCe/uvAZj6wgS78dYL2orGAMYJ//8AAf+/AAzuuQBY5ZoAsNZ2AMnKWgHKwUUOyr0yKMq7I0TKuhVmyr8NkMrGCrTJzw3Ns9kQ3l7dEd0PgACAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= \ No newline at end of file From 012ceece76ecc0e302593edd7ce4c3b01aef803e Mon Sep 17 00:00:00 2001 From: masarray Date: Tue, 14 Jul 2026 16:51:55 +0700 Subject: [PATCH 03/24] ci: apply repaired SCL workflow and layout changes --- .../apply-scl-workflow-layout-repair.yml | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/workflows/apply-scl-workflow-layout-repair.yml diff --git a/.github/workflows/apply-scl-workflow-layout-repair.yml b/.github/workflows/apply-scl-workflow-layout-repair.yml new file mode 100644 index 00000000..1835ebee --- /dev/null +++ b/.github/workflows/apply-scl-workflow-layout-repair.yml @@ -0,0 +1,46 @@ +name: Apply repaired SCL workflow and layout + +on: + push: + branches: [ fix/scl-workflow-layout-repair ] + +permissions: + contents: write + +jobs: + apply: + runs-on: ubuntu-latest + steps: + - name: Checkout repair branch + uses: actions/checkout@v4 + with: + ref: fix/scl-workflow-layout-repair + fetch-depth: 0 + + - name: Apply reviewed source transformations + shell: bash + run: | + set -euo pipefail + python3 .repair/apply.py + base64 --decode .repair/app-icon.b64 > Assets/app-icon.ico + + grep -q 'Content="{Binding ControlPendingConfirmationLabel}"' MainWindow.xaml + ! grep -q 'Content="Confirm Open"' MainWindow.xaml + ! grep -q 'Content="Confirm Close"' MainWindow.xaml + grep -q 'TryClaimControlConfirmation' MainWindow.xaml.cs + grep -q 'SclFindingAggregator.Group(actionableFindings)' MainWindow.xaml.cs + grep -q '1.6.9' ArIED61850Tester.csproj + test -s Assets/app-icon.ico + + rm -rf .repair + rm -f .github/workflows/apply-scl-workflow-layout-repair.yml + + - name: Commit repaired implementation + shell: bash + run: | + set -euo pipefail + git config user.name "github-actions[bot]" + git config user.email "4189826+github-actions[bot]@users.noreply.github.com" + git add -A + git commit -m "fix: repair SCL-first workflow and compact application layout" + git push origin HEAD:fix/scl-workflow-layout-repair From 84d90c1055b521b9bad622833594fc67fb57c74c Mon Sep 17 00:00:00 2001 From: masarray Date: Tue, 14 Jul 2026 16:52:39 +0700 Subject: [PATCH 04/24] ci: run repair gate from pull request --- .github/workflows/apply-scl-workflow-layout-repair.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/apply-scl-workflow-layout-repair.yml b/.github/workflows/apply-scl-workflow-layout-repair.yml index 1835ebee..dd68dc5c 100644 --- a/.github/workflows/apply-scl-workflow-layout-repair.yml +++ b/.github/workflows/apply-scl-workflow-layout-repair.yml @@ -1,14 +1,15 @@ name: Apply repaired SCL workflow and layout on: - push: - branches: [ fix/scl-workflow-layout-repair ] + pull_request: + branches: [ main ] permissions: contents: write jobs: apply: + if: ${{ github.event.pull_request.head.ref == 'fix/scl-workflow-layout-repair' }} runs-on: ubuntu-latest steps: - name: Checkout repair branch From 130e523eeea73c630e2af4a9444f033761686b38 Mon Sep 17 00:00:00 2001 From: masarray Date: Tue, 14 Jul 2026 16:54:29 +0700 Subject: [PATCH 05/24] ci: repair staged documentation writer before apply --- .../apply-scl-workflow-layout-repair.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/workflows/apply-scl-workflow-layout-repair.yml b/.github/workflows/apply-scl-workflow-layout-repair.yml index dd68dc5c..b9780202 100644 --- a/.github/workflows/apply-scl-workflow-layout-repair.yml +++ b/.github/workflows/apply-scl-workflow-layout-repair.yml @@ -22,6 +22,23 @@ jobs: shell: bash run: | set -euo pipefail + python3 - <<'PY' + from pathlib import Path + path = Path('.repair/apply.py') + text = path.read_text(encoding='utf-8') + marker = 'Path("docs/SCL_SMART_DYNAMIC_REPORTING.md").write_text(' + start = text.index(marker) + replacement = '''Path("docs/SCL_SMART_DYNAMIC_REPORTING.md").write_text( + "# SCL-assisted Smart Dynamic reporting\\n\\n" + "Opening an SCL file is an offline operation. After the model is loaded, ArIED opens the signal-selection workflow immediately. Saving a valid live selection continues to endpoint binding, MMS association, and monitoring.\\n\\n" + "When selected signals have complete static DataSet and ReportControl coverage, ArIED prefers that configuration. When coverage is missing or partial, ArIED prepares an association-scoped dynamic DataSet from the uncovered selected signals and asks the native engine to use a safe free RCB. MMS polling remains the final fallback.\\n\\n" + "Unassigned or unresolved ReportControl DataSet references reported as warnings are retained in the typed SCL workspace but are not promoted into noisy live Diagnostics because they are capability hints handled by Smart Dynamic reporting. A positively resolved empty DataSet and other blocking SCL findings remain actionable diagnostics.\\n\\n" + "The command panel expands automatically only once per IED, after the live control model has produced at least one proven executable command row.\\n", + encoding="utf-8") + ''' + path.write_text(text[:start] + replacement, encoding='utf-8') + PY + python3 .repair/apply.py base64 --decode .repair/app-icon.b64 > Assets/app-icon.ico From 70689d9e6584aec50dcfd6d5bf4a68dbd67e8cb4 Mon Sep 17 00:00:00 2001 From: masarray Date: Tue, 14 Jul 2026 16:56:10 +0700 Subject: [PATCH 06/24] ci: capture exact repair gate failure --- .../apply-scl-workflow-layout-repair.yml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/apply-scl-workflow-layout-repair.yml b/.github/workflows/apply-scl-workflow-layout-repair.yml index b9780202..3fc51c8e 100644 --- a/.github/workflows/apply-scl-workflow-layout-repair.yml +++ b/.github/workflows/apply-scl-workflow-layout-repair.yml @@ -39,7 +39,20 @@ jobs: path.write_text(text[:start] + replacement, encoding='utf-8') PY - python3 .repair/apply.py + rm -f .repair/apply-error.txt + set +e + python3 .repair/apply.py > .repair/apply-error.txt 2>&1 + status=$? + set -e + if [ "$status" -ne 0 ]; then + git config user.name "github-actions[bot]" + git config user.email "4189826+github-actions[bot]@users.noreply.github.com" + git add .repair/apply-error.txt + git commit -m "debug: capture repair gate failure" + git push origin HEAD:fix/scl-workflow-layout-repair + exit "$status" + fi + base64 --decode .repair/app-icon.b64 > Assets/app-icon.ico grep -q 'Content="{Binding ControlPendingConfirmationLabel}"' MainWindow.xaml From 6d044d5ff24128e16ddfd591a82182c16cb800ce Mon Sep 17 00:00:00 2001 From: masarray Date: Tue, 14 Jul 2026 16:59:16 +0700 Subject: [PATCH 07/24] ci: make repair transformations robust and syntax-valid --- .repair/apply.py | 366 +++++++++-------------------------------------- 1 file changed, 70 insertions(+), 296 deletions(-) diff --git a/.repair/apply.py b/.repair/apply.py index 765ec70c..f56ee4e3 100644 --- a/.repair/apply.py +++ b/.repair/apply.py @@ -10,328 +10,105 @@ def write(path: str, text: str) -> None: Path(path).write_text(text, encoding="utf-8", newline="\n") -def replace_once(text: str, old: str, new: str, label: str) -> str: - if new in text: +def sub_once(text: str, pattern: str, replacement: str, label: str, flags: int = 0) -> str: + if replacement in text: return text - count = text.count(old) + updated, count = re.subn(pattern, replacement, text, count=1, flags=flags) if count != 1: - raise SystemExit(f"{label}: expected exactly one source block, found {count}") - return text.replace(old, new, 1) + raise SystemExit(f"{label}: expected one match, found {count}") + return updated -# --------------------------------------------------------------------------- -# Main window layout. Keep the P0 single Confirm button untouched. -# --------------------------------------------------------------------------- xaml_path = "MainWindow.xaml" xaml = read(xaml_path) -xaml = replace_once( +xaml = sub_once( xaml, - ''' - - - - - ''', - ''' - - - - ''', - "main row definitions") - -header_pattern = re.compile( - r'''\n \n''' - r''' .*?\n \n\n''' - r''' \n''' - r''' \s*\s*\s*\s*\s*''', + ''' \n \n \n \n ''', + "main grid rows", re.S) -header_replacement = ''' - - - - - - - ''' -explorer_header_new = ''' - - - - - - - - - - - - - ''' -xaml = replace_once(xaml, explorer_header_old, explorer_header_new, "IED Explorer project actions") +if "Compact navigation row: the large top application bar is removed" not in xaml: + pattern = ( + r'''\n \n''' + r''' .*?\n \n\n''' + r''' \n''' + r''' \n \s*''' + r'''\s*''' + r'''\s*''' + r'''\s*''' + r'''\s*''') + replacement = ''' \n \n \n \n \n \n \n \n \n \n \n \n \n ''' + xaml = sub_once(xaml, pattern, replacement, "IED Explorer actions", re.S) -# Guard against the regression that caused the broken patch: P0 must still use one -# stable Confirm button bound to ControlPendingConfirmationLabel. if xaml.count('Content="{Binding ControlPendingConfirmationLabel}"') != 1: - raise SystemExit("P0 regression guard: stable single Confirm button is missing") + raise SystemExit("P0 guard: stable single Confirm button is missing") if 'Content="Confirm Open"' in xaml or 'Content="Confirm Close"' in xaml: - raise SystemExit("P0 regression guard: legacy dual Confirm buttons detected") - + raise SystemExit("P0 guard: legacy dual Confirm buttons detected") write(xaml_path, xaml) -# --------------------------------------------------------------------------- -# SCL workflow, smart dynamic reporting, quiet capability diagnostics, and -# one-time command panel expansion. -# --------------------------------------------------------------------------- cs_path = "MainWindow.xaml.cs" cs = read(cs_path) -cs = replace_once( - cs, - ''' private bool _signalSelectionWizardOpen; - private bool _connectAllInProgress; -''', - ''' private bool _signalSelectionWizardOpen; - private bool _connectAllInProgress; - private readonly HashSet _autoExpandedCommandDevices = new(StringComparer.OrdinalIgnoreCase); -''', - "one-time command expansion state") - -cs = replace_once( - cs, - ''' Raise(nameof(ActiveIedTitle)); - Raise(nameof(ActiveIedSubtitle)); - RaiseWorkspaceCounts(); - // ctlModel inspection is preloaded independently of the Expander. Avoid -''', - ''' Raise(nameof(ActiveIedTitle)); - Raise(nameof(ActiveIedSubtitle)); - RaiseWorkspaceCounts(); - TryAutoExpandCommandPanelOnce(_selectedDevice); - // ctlModel inspection is preloaded independently of the Expander. Avoid -''', - "selected-device expansion hook") - -cs = replace_once( - cs, - ''' var status = $"{sourceName}: {document.Ieds.Count} IED/AP workspace(s), {offlineCount} offline model(s), {endpointCount} MMS endpoint(s) — {added} added, {refreshed} refreshed, {retained} active retained."; - SetStatus(status); - AddLog("INFO", "SCL", status); -''', - ''' var status = $"{sourceName}: {document.Ieds.Count} IED/AP workspace(s), {offlineCount} offline model(s), {endpointCount} MMS endpoint(s) — {added} added, {refreshed} refreshed, {retained} active retained."; - SetStatus(status); - AddLog("INFO", "SCL", status); - - if (firstImported != null && firstImported.Signals.Count > 0) - { - AddLog("INFO", "SCL", $"{firstImported.Name}: SCL model ready. Choose signals; saving the selection will continue to endpoint binding, connection, and monitoring."); - await OpenSignalSelectionWizardAsync(firstImported); - } -''', - "Open SCL selection workflow") +if "_autoExpandedCommandDevices" not in cs: + cs = sub_once(cs, r'''( private bool _connectAllInProgress;\n)''', r'''\1 private readonly HashSet _autoExpandedCommandDevices = new(StringComparer.OrdinalIgnoreCase);\n''', "command auto-expand state") -cs = replace_once( - cs, - ''' device.AllowDynamicDataSetWrites = false; - device.SclWorkspace = workspace; -''', - ''' var allowDynamicReporting = ShouldAllowDynamicReportingForScl(signals); - device.AllowDynamicDataSetWrites = allowDynamicReporting; - device.SclWorkspace = workspace; -''', - "SCL dynamic reporting policy") +if "TryAutoExpandCommandPanelOnce(_selectedDevice);" not in cs: + cs = sub_once(cs, r'''( RaiseWorkspaceCounts\(\);\n)( // ctlModel inspection)''', r'''\1 TryAutoExpandCommandPanelOnce(_selectedDevice);\n\2''', "selected-device expansion hook") -cs = replace_once( - cs, - ''' device.Status = workspace.RequiresEndpointBinding ? "SCL model ready — bind endpoint" : "SCL model ready"; - device.Detail = workspace.RequiresEndpointBinding - ? "LD/LN/DO/DA are available offline. Press Play to bind an MMS endpoint; no discovery traffic was sent while opening the file." - : "LD/LN/DO/DA were loaded offline. Play performs a fast MMS association; Re-scan performs full design-versus-live verification."; - device.AcquisitionMode = "SCL offline design model"; -''', - ''' device.Status = workspace.RequiresEndpointBinding ? "SCL model ready — bind endpoint" : "SCL model ready"; - device.Detail = allowDynamicReporting - ? (workspace.RequiresEndpointBinding - ? "LD/LN/DO/DA are available offline. Static report coverage is incomplete; after signal selection and endpoint binding, ArIED will create an association-scoped dynamic DataSet and use a safe free RCB before polling fallback." - : "LD/LN/DO/DA were loaded offline. Static report coverage is incomplete; ArIED will use static coverage where available and create an association-scoped dynamic DataSet for uncovered selected signals before polling fallback.") - : (workspace.RequiresEndpointBinding - ? "LD/LN/DO/DA are available offline. Press Play to bind an MMS endpoint; no discovery traffic was sent while opening the file." - : "LD/LN/DO/DA were loaded offline. Play performs a fast MMS association; Re-scan performs full design-versus-live verification."); - device.AcquisitionMode = allowDynamicReporting - ? "SCL design • Smart Dynamic reporting prepared" - : "SCL offline design model"; -''', - "SCL workspace detail") +if "SCL model ready. Choose signals" not in cs: + cs = sub_once(cs, r'''( AddLog\("INFO", "SCL", status\);\n)''', r'''\1\n if (firstImported != null && firstImported.Signals.Count > 0)\n {\n AddLog("INFO", "SCL", $"{firstImported.Name}: SCL model ready. Choose signals; saving the selection will continue to endpoint binding, connection, and monitoring.");\n await OpenSignalSelectionWizardAsync(firstImported);\n }\n''', "Open SCL selection workflow") -log_method_pattern = re.compile( - r''' private void LogSclFindings\(string sourceName, IReadOnlyList findings\)\n \{.*?\n \}\n\n private bool EnsureSclEndpointBinding''', - re.S) -log_method_new = ''' private void LogSclFindings(string sourceName, IReadOnlyList findings) - { - var actionableFindings = findings - .Where(finding => !IsSmartDynamicCapabilityHint(finding)) - .ToArray(); - if (actionableFindings.Length == 0) - return; - - var groups = SclFindingAggregator.Group(actionableFindings); - if (groups.Count != actionableFindings.Length) - { - AddLog( - "INFO", - "SCL", - $"{sourceName} • grouped {actionableFindings.Length} actionable finding(s) into {groups.Count} diagnostic group(s). Full typed evidence remains attached to the SCL workspace."); - } - - foreach (var group in groups.Take(40)) - { - AddLog( - SclFindingAggregator.ToLogLevel(group.Severity), - "SCL", - $"{sourceName} • {group.Code} [{group.Scope}]: {group.ToDiagnosticMessage()}"); - } - - if (groups.Count > 40) - { - var omittedRawCount = groups - .Skip(40) - .Sum(group => group.Count); - AddLog( - "WARN", - "SCL", - $"{groups.Count - 40} additional diagnostic group(s), representing {omittedRawCount} actionable finding(s), were omitted from the live log."); - } - - if (groups.Any(group => SclFindingAggregator.IsBlockingSeverity(group.Severity))) - MarkDiagnosticAlert(); - } - - private static bool IsSmartDynamicCapabilityHint(SclWorkspaceFinding finding) - { - if (!finding.Severity.Equals("Warning", StringComparison.OrdinalIgnoreCase)) - return false; +if "var allowDynamicReporting = ShouldAllowDynamicReportingForScl(signals);" not in cs: + cs = sub_once(cs, r''' device\.AllowDynamicDataSetWrites = false;\n''', ''' var allowDynamicReporting = ShouldAllowDynamicReportingForScl(signals);\n device.AllowDynamicDataSetWrites = allowDynamicReporting;\n''', "dynamic reporting policy") - return finding.Code.Equals("SCL_REPORT_DATASET_UNASSIGNED", StringComparison.OrdinalIgnoreCase) || - finding.Code.Equals("SCL_REPORT_DATASET_UNRESOLVED", StringComparison.OrdinalIgnoreCase); - } +if "Smart Dynamic reporting prepared" not in cs: + pattern = ( + r''' device\.Status = workspace\.RequiresEndpointBinding \? "SCL model ready — bind endpoint" : "SCL model ready";\n''' + r''' device\.Detail = workspace\.RequiresEndpointBinding\n''' + r''' \? "LD/LN/DO/DA are available offline\..*?"\n''' + r''' : "LD/LN/DO/DA were loaded offline\..*?";\n''' + r''' device\.AcquisitionMode = "SCL offline design model";''') + replacement = ''' device.Status = workspace.RequiresEndpointBinding ? "SCL model ready — bind endpoint" : "SCL model ready";\n device.Detail = allowDynamicReporting\n ? (workspace.RequiresEndpointBinding\n ? "LD/LN/DO/DA are available offline. Static report coverage is incomplete; after signal selection and endpoint binding, ArIED will create an association-scoped dynamic DataSet and use a safe free RCB before polling fallback."\n : "LD/LN/DO/DA were loaded offline. Static report coverage is incomplete; ArIED will use static coverage where available and create an association-scoped dynamic DataSet for uncovered selected signals before polling fallback.")\n : (workspace.RequiresEndpointBinding\n ? "LD/LN/DO/DA are available offline. Press Play to bind an MMS endpoint; no discovery traffic was sent while opening the file."\n : "LD/LN/DO/DA were loaded offline. Play performs a fast MMS association; Re-scan performs full design-versus-live verification.");\n device.AcquisitionMode = allowDynamicReporting\n ? "SCL design • Smart Dynamic reporting prepared"\n : "SCL offline design model";''' + cs = sub_once(cs, pattern, replacement, "SCL detail policy", re.S) - private static bool ShouldAllowDynamicReportingForScl(IReadOnlyCollection signals) - { - return signals.Any(signal => - signal.CanPublishAsSignal && - (string.IsNullOrWhiteSpace(signal.DataSetReference) || - string.IsNullOrWhiteSpace(signal.ReportControlReference))); - } - - private void TryAutoExpandCommandPanelOnce(Iec61850MonitorDevice? device) - { - if (device == null || CommandPanelExpander == null || device.CommandSignals.Count == 0) - return; - if (!_autoExpandedCommandDevices.Add(device.DeviceId)) - return; - - Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() => - { - if (ReferenceEquals(SelectedDevice, device) && CommandPanelExpander != null) - CommandPanelExpander.IsExpanded = true; - })); - } - - private bool EnsureSclEndpointBinding''' if "IsSmartDynamicCapabilityHint" not in cs: - cs, count = log_method_pattern.subn(log_method_new, cs, count=1) - if count != 1: - raise SystemExit(f"LogSclFindings replacement: expected one method, replaced {count}") + pattern = r''' private void LogSclFindings\(string sourceName, IReadOnlyList findings\)\n \{.*?\n \}\n\n private bool EnsureSclEndpointBinding''' + replacement = ''' private void LogSclFindings(string sourceName, IReadOnlyList findings)\n {\n var actionableFindings = findings\n .Where(finding => !IsSmartDynamicCapabilityHint(finding))\n .ToArray();\n if (actionableFindings.Length == 0)\n return;\n\n var groups = SclFindingAggregator.Group(actionableFindings);\n if (groups.Count != actionableFindings.Length)\n {\n AddLog(\n "INFO",\n "SCL",\n $"{sourceName} • grouped {actionableFindings.Length} actionable finding(s) into {groups.Count} diagnostic group(s). Full typed evidence remains attached to the SCL workspace.");\n }\n\n foreach (var group in groups.Take(40))\n {\n AddLog(\n SclFindingAggregator.ToLogLevel(group.Severity),\n "SCL",\n $"{sourceName} • {group.Code} [{group.Scope}]: {group.ToDiagnosticMessage()}");\n }\n\n if (groups.Count > 40)\n {\n var omittedRawCount = groups.Skip(40).Sum(group => group.Count);\n AddLog(\n "WARN",\n "SCL",\n $"{groups.Count - 40} additional diagnostic group(s), representing {omittedRawCount} actionable finding(s), were omitted from the live log.");\n }\n\n if (groups.Any(group => SclFindingAggregator.IsBlockingSeverity(group.Severity)))\n MarkDiagnosticAlert();\n }\n\n private static bool IsSmartDynamicCapabilityHint(SclWorkspaceFinding finding)\n {\n if (!finding.Severity.Equals("Warning", StringComparison.OrdinalIgnoreCase))\n return false;\n\n return finding.Code.Equals("SCL_REPORT_DATASET_UNASSIGNED", StringComparison.OrdinalIgnoreCase) ||\n finding.Code.Equals("SCL_REPORT_DATASET_UNRESOLVED", StringComparison.OrdinalIgnoreCase);\n }\n\n private static bool ShouldAllowDynamicReportingForScl(IReadOnlyCollection signals)\n {\n return signals.Any(signal =>\n signal.CanPublishAsSignal &&\n (string.IsNullOrWhiteSpace(signal.DataSetReference) ||\n string.IsNullOrWhiteSpace(signal.ReportControlReference)));\n }\n\n private void TryAutoExpandCommandPanelOnce(Iec61850MonitorDevice? device)\n {\n if (device == null || CommandPanelExpander == null || device.CommandSignals.Count == 0)\n return;\n if (!_autoExpandedCommandDevices.Add(device.DeviceId))\n return;\n\n Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>\n {\n if (ReferenceEquals(SelectedDevice, device) && CommandPanelExpander != null)\n CommandPanelExpander.IsExpanded = true;\n }));\n }\n\n private bool EnsureSclEndpointBinding''' + cs = sub_once(cs, pattern, replacement, "SCL diagnostic policy", re.S) -cs = replace_once( - cs, - ''' device.IpAddress = wizard.RelayIpAddress; - device.Port = wizard.MmsPort; - device.Status = "SCL model ready"; - device.Detail = "Endpoint bound locally. Play will fast-connect from the SCL design model; Re-scan performs full comparison."; - device.RefreshComputed(); -''', - ''' device.IpAddress = wizard.RelayIpAddress; - device.Port = wizard.MmsPort; - device.Status = "SCL model ready"; - device.Detail = device.AllowDynamicDataSetWrites - ? "Endpoint bound locally. Saving the selected signals will connect and arm Smart Dynamic reporting with a safe free RCB before polling fallback." - : "Endpoint bound locally. Play will fast-connect from the SCL design model; Re-scan performs full comparison."; - device.RefreshComputed(); -''', - "endpoint binding detail") +if "Saving the selected signals will connect and arm Smart Dynamic reporting" not in cs: + cs = sub_once(cs, r''' device\.Detail = "Endpoint bound locally\. Play will fast-connect from the SCL design model; Re-scan performs full comparison\.";''', ''' device.Detail = device.AllowDynamicDataSetWrites\n ? "Endpoint bound locally. Saving the selected signals will connect and arm Smart Dynamic reporting with a safe free RCB before polling fallback."\n : "Endpoint bound locally. Play will fast-connect from the SCL design model; Re-scan performs full comparison.";''', "endpoint detail") -cs = replace_once( - cs, - ''' device.RefreshCommandSignalProjection(); - RebuildControlFeedbackIndex(device); - SetStatus($"{device.Name}: refreshed {candidates.Length} control value(s)."); -''', - ''' device.RefreshCommandSignalProjection(); - RebuildControlFeedbackIndex(device); - TryAutoExpandCommandPanelOnce(device); - SetStatus($"{device.Name}: refreshed {candidates.Length} control value(s)."); -''', - "command list ready expansion") +if "TryAutoExpandCommandPanelOnce(device);" not in cs: + cs = sub_once(cs, r'''( device\.RefreshCommandSignalProjection\(\);\n RebuildControlFeedbackIndex\(device\);\n)''', r'''\1 TryAutoExpandCommandPanelOnce(device);\n''', "command-ready expansion") -cs = replace_once( - cs, - ''' AllowDynamicDataSetWrites = hasSclProvenance ? false : profile.AllowDynamicDataSetWrites, -''', - ''' AllowDynamicDataSetWrites = hasSclProvenance - ? ShouldAllowDynamicReportingForScl(cachedSignals) - : profile.AllowDynamicDataSetWrites, -''', - "restored SCL dynamic reporting policy") +if "? ShouldAllowDynamicReportingForScl(cachedSignals)" not in cs: + cs = sub_once(cs, r''' AllowDynamicDataSetWrites = hasSclProvenance \? false : profile\.AllowDynamicDataSetWrites,''', ''' AllowDynamicDataSetWrites = hasSclProvenance\n ? ShouldAllowDynamicReportingForScl(cachedSignals)\n : profile.AllowDynamicDataSetWrites,''', "project restore dynamic policy") -# P0 must remain present after all transformations. -required_p0_tokens = [ - "TryClaimControlConfirmation", - "ControlInspectionBusy", - "ControlCommandBusy", - "ExecuteClaimedControlAsync" -] -for token in required_p0_tokens: +for token in ("TryClaimControlConfirmation", "ControlInspectionBusy", "ControlCommandBusy", "ExecuteClaimedControlAsync"): if token not in cs: - raise SystemExit(f"P0 regression guard: missing {token}") - + raise SystemExit(f"P0 guard: missing {token}") +if "SclFindingAggregator.Group(actionableFindings)" not in cs: + raise SystemExit("P1 guard: grouped diagnostics missing") write(cs_path, cs) -# --------------------------------------------------------------------------- -# Release alignment. -# --------------------------------------------------------------------------- project_path = "ArIED61850Tester.csproj" project = read(project_path) -project = project.replace("1.6.8", "1.6.9") -project = project.replace("1.6.8.0", "1.6.9.0") -project = project.replace("1.6.8.0", "1.6.9.0") +for old, new in (("1.6.8.0", "1.6.9.0"), ("1.6.8", "1.6.9")): + project = project.replace(old, new) write(project_path, project) workflow_path = ".github/workflows/build.yml" @@ -339,16 +116,13 @@ def replace_once(text: str, old: str, new: str, label: str) -> str: write(workflow_path, workflow) publish_path = "scripts/publish-windows-portable.ps1" -publish = read(publish_path) -publish = publish.replace('[string]$Version = "1.6.8"', '[string]$Version = "1.6.9"') -publish = publish.replace("1.6.8 or v1.6.8", "1.6.9 or v1.6.9") +publish = read(publish_path).replace('"1.6.8"', '"1.6.9"').replace("1.6.8 or v1.6.8", "1.6.9 or v1.6.9") write(publish_path, publish) Path("docs/SCL_SMART_DYNAMIC_REPORTING.md").write_text( - """# SCL-assisted Smart Dynamic reporting\n\n" - "Opening an SCL file is an offline operation. After the model is loaded, ArIED opens the signal-selection workflow immediately. Saving a valid live selection continues to endpoint binding, MMS association, and monitoring.\n\n" - "When selected signals have complete static DataSet and ReportControl coverage, ArIED prefers that configuration. When coverage is missing or partial, ArIED prepares an association-scoped dynamic DataSet from the uncovered selected signals and asks the native engine to use a safe free RCB. MMS polling remains the final fallback.\n\n" - "Unassigned or unresolved ReportControl DataSet references reported as warnings are retained in the typed SCL workspace but are not promoted into noisy live Diagnostics because they are capability hints handled by Smart Dynamic reporting. A positively resolved empty DataSet and other blocking SCL findings remain actionable diagnostics.\n\n" - "The command panel expands automatically only once per IED, after the live control model has produced at least one proven executable command row.\n" - """, + "# SCL-assisted Smart Dynamic reporting\n\n" + "Opening an SCL file remains offline. After the model is loaded, ArIED opens signal selection immediately. Saving a valid live selection continues to endpoint binding, MMS association, and monitoring.\n\n" + "Static DataSet and ReportControl coverage is preferred when complete. Missing or partial coverage enables an association-scoped dynamic DataSet for uncovered selected signals and a safe free RCB before polling fallback.\n\n" + "Unassigned or unresolved report DataSet warnings remain in the typed SCL workspace but are not promoted into noisy live Diagnostics. Positively resolved empty DataSets and other blocking findings remain actionable.\n\n" + "The command panel expands once per IED only after at least one executable command row is ready.\n", encoding="utf-8") From 09d01276e3f372aa5ad14f048487fd47cd04e291 Mon Sep 17 00:00:00 2001 From: masarray Date: Tue, 14 Jul 2026 16:59:36 +0700 Subject: [PATCH 08/24] ci: run syntax-valid repair transformer --- .../apply-scl-workflow-layout-repair.yml | 33 ++----------------- 1 file changed, 2 insertions(+), 31 deletions(-) diff --git a/.github/workflows/apply-scl-workflow-layout-repair.yml b/.github/workflows/apply-scl-workflow-layout-repair.yml index 3fc51c8e..5fc00f7f 100644 --- a/.github/workflows/apply-scl-workflow-layout-repair.yml +++ b/.github/workflows/apply-scl-workflow-layout-repair.yml @@ -22,37 +22,8 @@ jobs: shell: bash run: | set -euo pipefail - python3 - <<'PY' - from pathlib import Path - path = Path('.repair/apply.py') - text = path.read_text(encoding='utf-8') - marker = 'Path("docs/SCL_SMART_DYNAMIC_REPORTING.md").write_text(' - start = text.index(marker) - replacement = '''Path("docs/SCL_SMART_DYNAMIC_REPORTING.md").write_text( - "# SCL-assisted Smart Dynamic reporting\\n\\n" - "Opening an SCL file is an offline operation. After the model is loaded, ArIED opens the signal-selection workflow immediately. Saving a valid live selection continues to endpoint binding, MMS association, and monitoring.\\n\\n" - "When selected signals have complete static DataSet and ReportControl coverage, ArIED prefers that configuration. When coverage is missing or partial, ArIED prepares an association-scoped dynamic DataSet from the uncovered selected signals and asks the native engine to use a safe free RCB. MMS polling remains the final fallback.\\n\\n" - "Unassigned or unresolved ReportControl DataSet references reported as warnings are retained in the typed SCL workspace but are not promoted into noisy live Diagnostics because they are capability hints handled by Smart Dynamic reporting. A positively resolved empty DataSet and other blocking SCL findings remain actionable diagnostics.\\n\\n" - "The command panel expands automatically only once per IED, after the live control model has produced at least one proven executable command row.\\n", - encoding="utf-8") - ''' - path.write_text(text[:start] + replacement, encoding='utf-8') - PY - - rm -f .repair/apply-error.txt - set +e - python3 .repair/apply.py > .repair/apply-error.txt 2>&1 - status=$? - set -e - if [ "$status" -ne 0 ]; then - git config user.name "github-actions[bot]" - git config user.email "4189826+github-actions[bot]@users.noreply.github.com" - git add .repair/apply-error.txt - git commit -m "debug: capture repair gate failure" - git push origin HEAD:fix/scl-workflow-layout-repair - exit "$status" - fi - + python3 -m py_compile .repair/apply.py + python3 .repair/apply.py base64 --decode .repair/app-icon.b64 > Assets/app-icon.ico grep -q 'Content="{Binding ControlPendingConfirmationLabel}"' MainWindow.xaml From 74b6f84ab0e77936b0e7505ceddc815af052b789 Mon Sep 17 00:00:00 2001 From: masarray Date: Tue, 14 Jul 2026 17:00:18 +0700 Subject: [PATCH 09/24] ci: capture transformation mismatch from valid repair script --- .../apply-scl-workflow-layout-repair.yml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/apply-scl-workflow-layout-repair.yml b/.github/workflows/apply-scl-workflow-layout-repair.yml index 5fc00f7f..422c8654 100644 --- a/.github/workflows/apply-scl-workflow-layout-repair.yml +++ b/.github/workflows/apply-scl-workflow-layout-repair.yml @@ -23,7 +23,20 @@ jobs: run: | set -euo pipefail python3 -m py_compile .repair/apply.py - python3 .repair/apply.py + rm -f .repair/apply-error.txt + set +e + python3 .repair/apply.py > .repair/apply-error.txt 2>&1 + status=$? + set -e + if [ "$status" -ne 0 ]; then + git config user.name "github-actions[bot]" + git config user.email "4189826+github-actions[bot]@users.noreply.github.com" + git add .repair/apply-error.txt + git commit -m "debug: capture repair transformation mismatch" + git push origin HEAD:fix/scl-workflow-layout-repair + exit "$status" + fi + base64 --decode .repair/app-icon.b64 > Assets/app-icon.ico grep -q 'Content="{Binding ControlPendingConfirmationLabel}"' MainWindow.xaml From 0ef87328ec6361273c4b864f712d0509b13c4b8d Mon Sep 17 00:00:00 2001 From: masarray Date: Tue, 14 Jul 2026 17:01:23 +0700 Subject: [PATCH 10/24] ci: capture all repair validation failures --- .../apply-scl-workflow-layout-repair.yml | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/.github/workflows/apply-scl-workflow-layout-repair.yml b/.github/workflows/apply-scl-workflow-layout-repair.yml index 422c8654..4db90eae 100644 --- a/.github/workflows/apply-scl-workflow-layout-repair.yml +++ b/.github/workflows/apply-scl-workflow-layout-repair.yml @@ -21,32 +21,35 @@ jobs: - name: Apply reviewed source transformations shell: bash run: | - set -euo pipefail - python3 -m py_compile .repair/apply.py + set -u rm -f .repair/apply-error.txt set +e - python3 .repair/apply.py > .repair/apply-error.txt 2>&1 + { + set -x + python3 -m py_compile .repair/apply.py + python3 .repair/apply.py + base64 --decode .repair/app-icon.b64 > Assets/app-icon.ico + grep -q 'Content="{Binding ControlPendingConfirmationLabel}"' MainWindow.xaml + ! grep -q 'Content="Confirm Open"' MainWindow.xaml + ! grep -q 'Content="Confirm Close"' MainWindow.xaml + grep -q 'TryClaimControlConfirmation' MainWindow.xaml.cs + grep -q 'SclFindingAggregator.Group(actionableFindings)' MainWindow.xaml.cs + grep -q '1.6.9' ArIED61850Tester.csproj + test -s Assets/app-icon.ico + } > .repair/apply-error.txt 2>&1 status=$? set -e if [ "$status" -ne 0 ]; then + git checkout -- MainWindow.xaml MainWindow.xaml.cs ArIED61850Tester.csproj .github/workflows/build.yml scripts/publish-windows-portable.ps1 2>/dev/null || true + rm -f docs/SCL_SMART_DYNAMIC_REPORTING.md Assets/app-icon.ico git config user.name "github-actions[bot]" git config user.email "4189826+github-actions[bot]@users.noreply.github.com" git add .repair/apply-error.txt - git commit -m "debug: capture repair transformation mismatch" + git commit -m "debug: capture repair validation failure" git push origin HEAD:fix/scl-workflow-layout-repair exit "$status" fi - base64 --decode .repair/app-icon.b64 > Assets/app-icon.ico - - grep -q 'Content="{Binding ControlPendingConfirmationLabel}"' MainWindow.xaml - ! grep -q 'Content="Confirm Open"' MainWindow.xaml - ! grep -q 'Content="Confirm Close"' MainWindow.xaml - grep -q 'TryClaimControlConfirmation' MainWindow.xaml.cs - grep -q 'SclFindingAggregator.Group(actionableFindings)' MainWindow.xaml.cs - grep -q '1.6.9' ArIED61850Tester.csproj - test -s Assets/app-icon.ico - rm -rf .repair rm -f .github/workflows/apply-scl-workflow-layout-repair.yml From ade588bfa9722b791892ad5fc2863c371b9ab26a Mon Sep 17 00:00:00 2001 From: masarray Date: Tue, 14 Jul 2026 17:02:31 +0700 Subject: [PATCH 11/24] ci: upload repair failure evidence --- .../apply-scl-workflow-layout-repair.yml | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/.github/workflows/apply-scl-workflow-layout-repair.yml b/.github/workflows/apply-scl-workflow-layout-repair.yml index 4db90eae..c5afba03 100644 --- a/.github/workflows/apply-scl-workflow-layout-repair.yml +++ b/.github/workflows/apply-scl-workflow-layout-repair.yml @@ -18,7 +18,9 @@ jobs: ref: fix/scl-workflow-layout-repair fetch-depth: 0 - - name: Apply reviewed source transformations + - name: Apply and validate reviewed transformations + id: transform + continue-on-error: true shell: bash run: | set -u @@ -40,20 +42,27 @@ jobs: status=$? set -e if [ "$status" -ne 0 ]; then - git checkout -- MainWindow.xaml MainWindow.xaml.cs ArIED61850Tester.csproj .github/workflows/build.yml scripts/publish-windows-portable.ps1 2>/dev/null || true - rm -f docs/SCL_SMART_DYNAMIC_REPORTING.md Assets/app-icon.ico - git config user.name "github-actions[bot]" - git config user.email "4189826+github-actions[bot]@users.noreply.github.com" - git add .repair/apply-error.txt - git commit -m "debug: capture repair validation failure" - git push origin HEAD:fix/scl-workflow-layout-repair exit "$status" fi rm -rf .repair rm -f .github/workflows/apply-scl-workflow-layout-repair.yml + - name: Upload repair failure evidence + if: steps.transform.outcome == 'failure' + uses: actions/upload-artifact@v4 + with: + name: scl-workflow-repair-error + path: .repair/apply-error.txt + if-no-files-found: error + + - name: Stop after repair failure + if: steps.transform.outcome == 'failure' + shell: bash + run: exit 1 + - name: Commit repaired implementation + if: steps.transform.outcome == 'success' shell: bash run: | set -euo pipefail From 5635f987ef38e266d88d068a5beef56ebd85a7ae Mon Sep 17 00:00:00 2001 From: masarray Date: Tue, 14 Jul 2026 17:03:38 +0700 Subject: [PATCH 12/24] ci: commit repaired source before staging cleanup --- .github/workflows/apply-scl-workflow-layout-repair.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/apply-scl-workflow-layout-repair.yml b/.github/workflows/apply-scl-workflow-layout-repair.yml index c5afba03..df2fe082 100644 --- a/.github/workflows/apply-scl-workflow-layout-repair.yml +++ b/.github/workflows/apply-scl-workflow-layout-repair.yml @@ -45,9 +45,6 @@ jobs: exit "$status" fi - rm -rf .repair - rm -f .github/workflows/apply-scl-workflow-layout-repair.yml - - name: Upload repair failure evidence if: steps.transform.outcome == 'failure' uses: actions/upload-artifact@v4 @@ -68,6 +65,6 @@ jobs: set -euo pipefail git config user.name "github-actions[bot]" git config user.email "4189826+github-actions[bot]@users.noreply.github.com" - git add -A + git add MainWindow.xaml MainWindow.xaml.cs ArIED61850Tester.csproj .github/workflows/build.yml scripts/publish-windows-portable.ps1 Assets/app-icon.ico docs/SCL_SMART_DYNAMIC_REPORTING.md git commit -m "fix: repair SCL-first workflow and compact application layout" git push origin HEAD:fix/scl-workflow-layout-repair From 6442c31c6bf24e8dc0a6184e1362c5f15960cb22 Mon Sep 17 00:00:00 2001 From: masarray Date: Tue, 14 Jul 2026 17:04:38 +0700 Subject: [PATCH 13/24] ci: export validated repaired source --- .../apply-scl-workflow-layout-repair.yml | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/.github/workflows/apply-scl-workflow-layout-repair.yml b/.github/workflows/apply-scl-workflow-layout-repair.yml index df2fe082..dd414f9c 100644 --- a/.github/workflows/apply-scl-workflow-layout-repair.yml +++ b/.github/workflows/apply-scl-workflow-layout-repair.yml @@ -5,7 +5,7 @@ on: branches: [ main ] permissions: - contents: write + contents: read jobs: apply: @@ -58,13 +58,17 @@ jobs: shell: bash run: exit 1 - - name: Commit repaired implementation + - name: Upload validated repaired source if: steps.transform.outcome == 'success' - shell: bash - run: | - set -euo pipefail - git config user.name "github-actions[bot]" - git config user.email "4189826+github-actions[bot]@users.noreply.github.com" - git add MainWindow.xaml MainWindow.xaml.cs ArIED61850Tester.csproj .github/workflows/build.yml scripts/publish-windows-portable.ps1 Assets/app-icon.ico docs/SCL_SMART_DYNAMIC_REPORTING.md - git commit -m "fix: repair SCL-first workflow and compact application layout" - git push origin HEAD:fix/scl-workflow-layout-repair + uses: actions/upload-artifact@v4 + with: + name: scl-workflow-repaired-source + path: | + MainWindow.xaml + MainWindow.xaml.cs + ArIED61850Tester.csproj + .github/workflows/build.yml + scripts/publish-windows-portable.ps1 + Assets/app-icon.ico + docs/SCL_SMART_DYNAMIC_REPORTING.md + if-no-files-found: error From 6ed6ca24a1bd306e23b052975dbaf6ef6c0624d3 Mon Sep 17 00:00:00 2001 From: masarray Date: Tue, 14 Jul 2026 17:06:17 +0700 Subject: [PATCH 14/24] ci: capture repaired source commit failure --- .../apply-scl-workflow-layout-repair.yml | 69 +++++++++---------- 1 file changed, 32 insertions(+), 37 deletions(-) diff --git a/.github/workflows/apply-scl-workflow-layout-repair.yml b/.github/workflows/apply-scl-workflow-layout-repair.yml index dd414f9c..6cfe528e 100644 --- a/.github/workflows/apply-scl-workflow-layout-repair.yml +++ b/.github/workflows/apply-scl-workflow-layout-repair.yml @@ -5,7 +5,7 @@ on: branches: [ main ] permissions: - contents: read + contents: write jobs: apply: @@ -20,55 +20,50 @@ jobs: - name: Apply and validate reviewed transformations id: transform + shell: bash + run: | + set -euo pipefail + python3 -m py_compile .repair/apply.py + python3 .repair/apply.py + base64 --decode .repair/app-icon.b64 > Assets/app-icon.ico + grep -q 'Content="{Binding ControlPendingConfirmationLabel}"' MainWindow.xaml + ! grep -q 'Content="Confirm Open"' MainWindow.xaml + ! grep -q 'Content="Confirm Close"' MainWindow.xaml + grep -q 'TryClaimControlConfirmation' MainWindow.xaml.cs + grep -q 'SclFindingAggregator.Group(actionableFindings)' MainWindow.xaml.cs + grep -q '1.6.9' ArIED61850Tester.csproj + test -s Assets/app-icon.ico + + - name: Commit repaired source + id: commit continue-on-error: true shell: bash run: | set -u - rm -f .repair/apply-error.txt set +e { set -x - python3 -m py_compile .repair/apply.py - python3 .repair/apply.py - base64 --decode .repair/app-icon.b64 > Assets/app-icon.ico - grep -q 'Content="{Binding ControlPendingConfirmationLabel}"' MainWindow.xaml - ! grep -q 'Content="Confirm Open"' MainWindow.xaml - ! grep -q 'Content="Confirm Close"' MainWindow.xaml - grep -q 'TryClaimControlConfirmation' MainWindow.xaml.cs - grep -q 'SclFindingAggregator.Group(actionableFindings)' MainWindow.xaml.cs - grep -q '1.6.9' ArIED61850Tester.csproj - test -s Assets/app-icon.ico - } > .repair/apply-error.txt 2>&1 + git status --short + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add MainWindow.xaml MainWindow.xaml.cs ArIED61850Tester.csproj .github/workflows/build.yml scripts/publish-windows-portable.ps1 Assets/app-icon.ico docs/SCL_SMART_DYNAMIC_REPORTING.md + git status --short + git commit -m "fix: repair SCL-first workflow and compact application layout" + git push origin HEAD:fix/scl-workflow-layout-repair + } > .repair/commit-error.txt 2>&1 status=$? set -e - if [ "$status" -ne 0 ]; then - exit "$status" - fi + exit "$status" - - name: Upload repair failure evidence - if: steps.transform.outcome == 'failure' + - name: Upload commit failure evidence + if: steps.commit.outcome == 'failure' uses: actions/upload-artifact@v4 with: - name: scl-workflow-repair-error - path: .repair/apply-error.txt + name: scl-workflow-repair-commit-error + path: .repair/commit-error.txt if-no-files-found: error - - name: Stop after repair failure - if: steps.transform.outcome == 'failure' + - name: Stop after commit failure + if: steps.commit.outcome == 'failure' shell: bash run: exit 1 - - - name: Upload validated repaired source - if: steps.transform.outcome == 'success' - uses: actions/upload-artifact@v4 - with: - name: scl-workflow-repaired-source - path: | - MainWindow.xaml - MainWindow.xaml.cs - ArIED61850Tester.csproj - .github/workflows/build.yml - scripts/publish-windows-portable.ps1 - Assets/app-icon.ico - docs/SCL_SMART_DYNAMIC_REPORTING.md - if-no-files-found: error From 841867e808a0bdf4648526392fc30ba5aa019862 Mon Sep 17 00:00:00 2001 From: masarray Date: Tue, 14 Jul 2026 17:07:29 +0700 Subject: [PATCH 15/24] ci: push repaired source without workflow mutation --- .../apply-scl-workflow-layout-repair.yml | 38 ++++--------------- 1 file changed, 7 insertions(+), 31 deletions(-) diff --git a/.github/workflows/apply-scl-workflow-layout-repair.yml b/.github/workflows/apply-scl-workflow-layout-repair.yml index 6cfe528e..5b3f7060 100644 --- a/.github/workflows/apply-scl-workflow-layout-repair.yml +++ b/.github/workflows/apply-scl-workflow-layout-repair.yml @@ -19,7 +19,6 @@ jobs: fetch-depth: 0 - name: Apply and validate reviewed transformations - id: transform shell: bash run: | set -euo pipefail @@ -35,35 +34,12 @@ jobs: test -s Assets/app-icon.ico - name: Commit repaired source - id: commit - continue-on-error: true shell: bash run: | - set -u - set +e - { - set -x - git status --short - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add MainWindow.xaml MainWindow.xaml.cs ArIED61850Tester.csproj .github/workflows/build.yml scripts/publish-windows-portable.ps1 Assets/app-icon.ico docs/SCL_SMART_DYNAMIC_REPORTING.md - git status --short - git commit -m "fix: repair SCL-first workflow and compact application layout" - git push origin HEAD:fix/scl-workflow-layout-repair - } > .repair/commit-error.txt 2>&1 - status=$? - set -e - exit "$status" - - - name: Upload commit failure evidence - if: steps.commit.outcome == 'failure' - uses: actions/upload-artifact@v4 - with: - name: scl-workflow-repair-commit-error - path: .repair/commit-error.txt - if-no-files-found: error - - - name: Stop after commit failure - if: steps.commit.outcome == 'failure' - shell: bash - run: exit 1 + set -euo pipefail + git checkout -- .github/workflows/build.yml + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add MainWindow.xaml MainWindow.xaml.cs ArIED61850Tester.csproj scripts/publish-windows-portable.ps1 Assets/app-icon.ico docs/SCL_SMART_DYNAMIC_REPORTING.md + git commit -m "fix: repair SCL-first workflow and compact application layout" + git push origin HEAD:fix/scl-workflow-layout-repair From d35d2ec2c78222a7bed417d8ed92c08143685a4b Mon Sep 17 00:00:00 2001 From: masarray Date: Tue, 14 Jul 2026 17:09:09 +0700 Subject: [PATCH 16/24] ci: publish repaired files through contents API --- .../apply-scl-workflow-layout-repair.yml | 103 ++++++++++++++---- 1 file changed, 84 insertions(+), 19 deletions(-) diff --git a/.github/workflows/apply-scl-workflow-layout-repair.yml b/.github/workflows/apply-scl-workflow-layout-repair.yml index 5b3f7060..39bf9812 100644 --- a/.github/workflows/apply-scl-workflow-layout-repair.yml +++ b/.github/workflows/apply-scl-workflow-layout-repair.yml @@ -19,27 +19,92 @@ jobs: fetch-depth: 0 - name: Apply and validate reviewed transformations + id: transform + continue-on-error: true shell: bash run: | - set -euo pipefail - python3 -m py_compile .repair/apply.py - python3 .repair/apply.py - base64 --decode .repair/app-icon.b64 > Assets/app-icon.ico - grep -q 'Content="{Binding ControlPendingConfirmationLabel}"' MainWindow.xaml - ! grep -q 'Content="Confirm Open"' MainWindow.xaml - ! grep -q 'Content="Confirm Close"' MainWindow.xaml - grep -q 'TryClaimControlConfirmation' MainWindow.xaml.cs - grep -q 'SclFindingAggregator.Group(actionableFindings)' MainWindow.xaml.cs - grep -q '1.6.9' ArIED61850Tester.csproj - test -s Assets/app-icon.ico - - - name: Commit repaired source + set -u + rm -f .repair/apply-error.txt + set +e + { + set -x + python3 -m py_compile .repair/apply.py + python3 .repair/apply.py + base64 --decode .repair/app-icon.b64 > Assets/app-icon.ico + grep -q 'Content="{Binding ControlPendingConfirmationLabel}"' MainWindow.xaml + ! grep -q 'Content="Confirm Open"' MainWindow.xaml + ! grep -q 'Content="Confirm Close"' MainWindow.xaml + grep -q 'TryClaimControlConfirmation' MainWindow.xaml.cs + grep -q 'SclFindingAggregator.Group(actionableFindings)' MainWindow.xaml.cs + grep -q '1.6.9' ArIED61850Tester.csproj + test -s Assets/app-icon.ico + } > .repair/apply-error.txt 2>&1 + status=$? + set -e + exit "$status" + + - name: Upload transformation failure evidence + if: steps.transform.outcome == 'failure' + uses: actions/upload-artifact@v4 + with: + name: scl-workflow-repair-error + path: .repair/apply-error.txt + if-no-files-found: error + + - name: Stop after transformation failure + if: steps.transform.outcome == 'failure' + shell: bash + run: exit 1 + + - name: Publish repaired non-workflow files + if: steps.transform.outcome == 'success' + env: + GH_TOKEN: ${{ github.token }} + TARGET_BRANCH: fix/scl-workflow-layout-repair shell: bash run: | set -euo pipefail - git checkout -- .github/workflows/build.yml - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add MainWindow.xaml MainWindow.xaml.cs ArIED61850Tester.csproj scripts/publish-windows-portable.ps1 Assets/app-icon.ico docs/SCL_SMART_DYNAMIC_REPORTING.md - git commit -m "fix: repair SCL-first workflow and compact application layout" - git push origin HEAD:fix/scl-workflow-layout-repair + + update_existing() { + local path="$1" + local current_sha local_sha content payload + current_sha=$(gh api "repos/$GITHUB_REPOSITORY/contents/$path?ref=$TARGET_BRANCH" --jq .sha) + local_sha=$(git hash-object "$path") + if [ "$current_sha" = "$local_sha" ]; then + echo "$path already current" + return + fi + content=$(base64 -w0 "$path") + payload=$(jq -n \ + --arg message "fix: publish repaired $path" \ + --arg content "$content" \ + --arg sha "$current_sha" \ + --arg branch "$TARGET_BRANCH" \ + '{message:$message,content:$content,sha:$sha,branch:$branch}') + gh api --method PUT "repos/$GITHUB_REPOSITORY/contents/$path" --input - <<<"$payload" >/dev/null + } + + create_or_update() { + local path="$1" + local current_sha local_sha content payload + local_sha=$(git hash-object "$path") + current_sha=$(gh api "repos/$GITHUB_REPOSITORY/contents/$path?ref=$TARGET_BRANCH" --jq .sha 2>/dev/null || true) + if [ -n "$current_sha" ] && [ "$current_sha" = "$local_sha" ]; then + echo "$path already current" + return + fi + content=$(base64 -w0 "$path") + if [ -n "$current_sha" ]; then + payload=$(jq -n --arg message "fix: publish repaired $path" --arg content "$content" --arg sha "$current_sha" --arg branch "$TARGET_BRANCH" '{message:$message,content:$content,sha:$sha,branch:$branch}') + else + payload=$(jq -n --arg message "docs: add Smart Dynamic SCL workflow" --arg content "$content" --arg branch "$TARGET_BRANCH" '{message:$message,content:$content,branch:$branch}') + fi + gh api --method PUT "repos/$GITHUB_REPOSITORY/contents/$path" --input - <<<"$payload" >/dev/null + } + + update_existing MainWindow.xaml + update_existing MainWindow.xaml.cs + update_existing ArIED61850Tester.csproj + update_existing scripts/publish-windows-portable.ps1 + update_existing Assets/app-icon.ico + create_or_update docs/SCL_SMART_DYNAMIC_REPORTING.md From f7b61d6d01398e4f1911d79429da64034674c17c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 14 Jul 2026 10:09:24 +0000 Subject: [PATCH 17/24] fix: publish repaired MainWindow.xaml --- MainWindow.xaml | 58 ++++++++++++++++++------------------------------- 1 file changed, 21 insertions(+), 37 deletions(-) diff --git a/MainWindow.xaml b/MainWindow.xaml index 4f3b604d..9ba3ceb1 100644 --- a/MainWindow.xaml +++ b/MainWindow.xaml @@ -8,45 +8,13 @@ Icon="Assets/app-icon.ico" Closing="Window_Closing"> - - - - - - - - - - - - - - - - - - - + @@ -815,7 +799,7 @@ - + From e6cb36743f306122851403537060781dfb216453 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 14 Jul 2026 10:09:26 +0000 Subject: [PATCH 18/24] fix: publish repaired MainWindow.xaml.cs --- MainWindow.xaml.cs | 82 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 67 insertions(+), 15 deletions(-) diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index 223d5a95..a0cee355 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -44,6 +44,7 @@ public partial class MainWindow : Window, INotifyPropertyChanged private bool _hasUnreadDiagnosticError; private bool _signalSelectionWizardOpen; private bool _connectAllInProgress; + private readonly HashSet _autoExpandedCommandDevices = new(StringComparer.OrdinalIgnoreCase); public ObservableCollection Devices { get; } = new(); public BulkObservableCollection GlobalPoints { get; } = new(); @@ -85,6 +86,7 @@ public Iec61850MonitorDevice? SelectedDevice Raise(nameof(ActiveIedTitle)); Raise(nameof(ActiveIedSubtitle)); RaiseWorkspaceCounts(); + TryAutoExpandCommandPanelOnce(_selectedDevice); // ctlModel inspection is preloaded independently of the Expander. Avoid // changing the row set after the panel's first frame has already painted. } @@ -223,6 +225,12 @@ private async void OpenScl_Click(object sender, RoutedEventArgs e) var status = $"{sourceName}: {document.Ieds.Count} IED/AP workspace(s), {offlineCount} offline model(s), {endpointCount} MMS endpoint(s) — {added} added, {refreshed} refreshed, {retained} active retained."; SetStatus(status); AddLog("INFO", "SCL", status); + + if (firstImported != null && firstImported.Signals.Count > 0) + { + AddLog("INFO", "SCL", $"{firstImported.Name}: SCL model ready. Choose signals; saving the selection will continue to endpoint binding, connection, and monitoring."); + await OpenSignalSelectionWizardAsync(firstImported); + } } catch (OperationCanceledException) { @@ -272,7 +280,8 @@ private void ApplySclWorkspaceToDevice( device.Port = 102; } - device.AllowDynamicDataSetWrites = false; + var allowDynamicReporting = ShouldAllowDynamicReportingForScl(signals); + device.AllowDynamicDataSetWrites = allowDynamicReporting; device.SclWorkspace = workspace; device.SclComparison = null; device.SclSourcePath = document.SourcePath; @@ -281,10 +290,16 @@ private void ApplySclWorkspaceToDevice( device.SclAccessPointName = workspace.AccessPointName; device.HasDiscoveryCache = signals.Count > 0; device.Status = workspace.RequiresEndpointBinding ? "SCL model ready — bind endpoint" : "SCL model ready"; - device.Detail = workspace.RequiresEndpointBinding - ? "LD/LN/DO/DA are available offline. Press Play to bind an MMS endpoint; no discovery traffic was sent while opening the file." - : "LD/LN/DO/DA were loaded offline. Play performs a fast MMS association; Re-scan performs full design-versus-live verification."; - device.AcquisitionMode = "SCL offline design model"; + device.Detail = allowDynamicReporting + ? (workspace.RequiresEndpointBinding + ? "LD/LN/DO/DA are available offline. Static report coverage is incomplete; after signal selection and endpoint binding, ArIED will create an association-scoped dynamic DataSet and use a safe free RCB before polling fallback." + : "LD/LN/DO/DA were loaded offline. Static report coverage is incomplete; ArIED will use static coverage where available and create an association-scoped dynamic DataSet for uncovered selected signals before polling fallback.") + : (workspace.RequiresEndpointBinding + ? "LD/LN/DO/DA are available offline. Press Play to bind an MMS endpoint; no discovery traffic was sent while opening the file." + : "LD/LN/DO/DA were loaded offline. Play performs a fast MMS association; Re-scan performs full design-versus-live verification."); + device.AcquisitionMode = allowDynamicReporting + ? "SCL design • Smart Dynamic reporting prepared" + : "SCL offline design model"; foreach (var signal in signals) { @@ -306,16 +321,19 @@ private static string BuildSclWorkspaceSummary(SclIedWorkspace workspace) private void LogSclFindings(string sourceName, IReadOnlyList findings) { - if (findings.Count == 0) + var actionableFindings = findings + .Where(finding => !IsSmartDynamicCapabilityHint(finding)) + .ToArray(); + if (actionableFindings.Length == 0) return; - var groups = SclFindingAggregator.Group(findings); - if (groups.Count != findings.Count) + var groups = SclFindingAggregator.Group(actionableFindings); + if (groups.Count != actionableFindings.Length) { AddLog( "INFO", "SCL", - $"{sourceName} • grouped {findings.Count} raw finding(s) into {groups.Count} diagnostic group(s). Full typed evidence remains attached to the SCL workspace."); + $"{sourceName} • grouped {actionableFindings.Length} actionable finding(s) into {groups.Count} diagnostic group(s). Full typed evidence remains attached to the SCL workspace."); } foreach (var group in groups.Take(40)) @@ -328,19 +346,48 @@ private void LogSclFindings(string sourceName, IReadOnlyList 40) { - var omittedRawCount = groups - .Skip(40) - .Sum(group => group.Count); + var omittedRawCount = groups.Skip(40).Sum(group => group.Count); AddLog( "WARN", "SCL", - $"{groups.Count - 40} additional diagnostic group(s), representing {omittedRawCount} raw finding(s), were omitted from the live log."); + $"{groups.Count - 40} additional diagnostic group(s), representing {omittedRawCount} actionable finding(s), were omitted from the live log."); } if (groups.Any(group => SclFindingAggregator.IsBlockingSeverity(group.Severity))) MarkDiagnosticAlert(); } + private static bool IsSmartDynamicCapabilityHint(SclWorkspaceFinding finding) + { + if (!finding.Severity.Equals("Warning", StringComparison.OrdinalIgnoreCase)) + return false; + + return finding.Code.Equals("SCL_REPORT_DATASET_UNASSIGNED", StringComparison.OrdinalIgnoreCase) || + finding.Code.Equals("SCL_REPORT_DATASET_UNRESOLVED", StringComparison.OrdinalIgnoreCase); + } + + private static bool ShouldAllowDynamicReportingForScl(IReadOnlyCollection signals) + { + return signals.Any(signal => + signal.CanPublishAsSignal && + (string.IsNullOrWhiteSpace(signal.DataSetReference) || + string.IsNullOrWhiteSpace(signal.ReportControlReference))); + } + + private void TryAutoExpandCommandPanelOnce(Iec61850MonitorDevice? device) + { + if (device == null || CommandPanelExpander == null || device.CommandSignals.Count == 0) + return; + if (!_autoExpandedCommandDevices.Add(device.DeviceId)) + return; + + Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() => + { + if (ReferenceEquals(SelectedDevice, device) && CommandPanelExpander != null) + CommandPanelExpander.IsExpanded = true; + })); + } + private bool EnsureSclEndpointBinding(Iec61850MonitorDevice device) { if (!device.RequiresEndpointBinding) @@ -357,7 +404,9 @@ private bool EnsureSclEndpointBinding(Iec61850MonitorDevice device) device.IpAddress = wizard.RelayIpAddress; device.Port = wizard.MmsPort; device.Status = "SCL model ready"; - device.Detail = "Endpoint bound locally. Play will fast-connect from the SCL design model; Re-scan performs full comparison."; + device.Detail = device.AllowDynamicDataSetWrites + ? "Endpoint bound locally. Saving the selected signals will connect and arm Smart Dynamic reporting with a safe free RCB before polling fallback." + : "Endpoint bound locally. Play will fast-connect from the SCL design model; Re-scan performs full comparison."; device.RefreshComputed(); NewDeviceIp = device.IpAddress; NewDevicePort = device.Port.ToString(CultureInfo.InvariantCulture); @@ -1024,6 +1073,7 @@ await Task.WhenAll(candidates.Select(async signal => device.RefreshCommandSignalProjection(); RebuildControlFeedbackIndex(device); + TryAutoExpandCommandPanelOnce(device); SetStatus($"{device.Name}: refreshed {candidates.Length} control value(s)."); } @@ -1677,7 +1727,9 @@ private async void OpenProject_Click(object sender, RoutedEventArgs e) LogicalDeviceSummary = profile.LogicalDeviceSummary, IpAddress = profile.IpAddress, Port = profile.Port <= 0 ? 102 : profile.Port, - AllowDynamicDataSetWrites = hasSclProvenance ? false : profile.AllowDynamicDataSetWrites, + AllowDynamicDataSetWrites = hasSclProvenance + ? ShouldAllowDynamicReportingForScl(cachedSignals) + : profile.AllowDynamicDataSetWrites, SclWorkspace = restoredSclWorkspace, SclSourcePath = profile.SclSourcePath, SclSourceSha256 = profile.SclSourceSha256, From a7f163555f5329071c11a5779a2b2d948b3cb304 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 14 Jul 2026 10:09:27 +0000 Subject: [PATCH 19/24] fix: publish repaired ArIED61850Tester.csproj --- ArIED61850Tester.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ArIED61850Tester.csproj b/ArIED61850Tester.csproj index c23d31f4..901e0ebd 100644 --- a/ArIED61850Tester.csproj +++ b/ArIED61850Tester.csproj @@ -14,9 +14,9 @@ ArIED 61850 ArIED 61850 — IEC 61850 Engineering Workstation Windows IEC 61850 engineering workstation for SCL-assisted project setup, live MMS model discovery, independent multi-IED monitoring, reporting diagnostics, sequence of events, and guarded Smart Control. - 1.6.8 - 1.6.8.0 - 1.6.8.0 + 1.6.9 + 1.6.9.0 + 1.6.9.0 iec61850;iec-61850;mms;scl;ied-explorer;multi-ied-monitoring;relay-testing;substation-automation;digital-substation;report-control-block;sequence-of-events;smart-control;wpf;dotnet GPL-3.0-or-later $(ARIEC61850_PROJECT) From 35e79dc114cbb064d5a2f15dc22765367c0620c8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 14 Jul 2026 10:09:28 +0000 Subject: [PATCH 20/24] fix: publish repaired scripts/publish-windows-portable.ps1 --- scripts/publish-windows-portable.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/publish-windows-portable.ps1 b/scripts/publish-windows-portable.ps1 index 3e24c65d..b5ec22ff 100644 --- a/scripts/publish-windows-portable.ps1 +++ b/scripts/publish-windows-portable.ps1 @@ -1,5 +1,5 @@ param( - [string]$Version = "1.6.8", + [string]$Version = "1.6.9", [string]$Runtime = "win-x64", [bool]$SingleFile = $true, [bool]$SelfContained = $true, @@ -23,7 +23,7 @@ if ($normalizedVersion.StartsWith("v", [System.StringComparison]::OrdinalIgnoreC $normalizedVersion = $normalizedVersion.Substring(1) } if ($normalizedVersion -notmatch '^\d+\.\d+\.\d+([-.][0-9A-Za-z.-]+)?$') { - throw "Invalid version '$Version'. Use a value such as 1.6.8 or v1.6.8." + throw "Invalid version '$Version'. Use a value such as 1.6.9 or v1.6.9." } $outputRoot = Join-Path $root "dist" From 5aec5eb0625fa5ae402c9677ab5eddcb19788d90 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 14 Jul 2026 10:09:30 +0000 Subject: [PATCH 21/24] fix: publish repaired Assets/app-icon.ico --- Assets/app-icon.ico | Bin 40892 -> 7500 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Assets/app-icon.ico b/Assets/app-icon.ico index c1d489c056b5261357dcf5451e9001d594b7058f..ffadfc3238299b76cfdcfa5f60ea8e718eaaf14b 100644 GIT binary patch literal 7500 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 a4QK3IU6pw?P#NbL`;#-3{>paPkN+S1blS%N literal 40892 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

XLs6P!s`oV5 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 Date: Tue, 14 Jul 2026 10:09:31 +0000 Subject: [PATCH 22/24] fix: publish repaired docs/SCL_SMART_DYNAMIC_REPORTING.md --- docs/SCL_SMART_DYNAMIC_REPORTING.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 docs/SCL_SMART_DYNAMIC_REPORTING.md diff --git a/docs/SCL_SMART_DYNAMIC_REPORTING.md b/docs/SCL_SMART_DYNAMIC_REPORTING.md new file mode 100644 index 00000000..96e8ddc9 --- /dev/null +++ b/docs/SCL_SMART_DYNAMIC_REPORTING.md @@ -0,0 +1,9 @@ +# SCL-assisted Smart Dynamic reporting + +Opening an SCL file remains offline. After the model is loaded, ArIED opens signal selection immediately. Saving a valid live selection continues to endpoint binding, MMS association, and monitoring. + +Static DataSet and ReportControl coverage is preferred when complete. Missing or partial coverage enables an association-scoped dynamic DataSet for uncovered selected signals and a safe free RCB before polling fallback. + +Unassigned or unresolved report DataSet warnings remain in the typed SCL workspace but are not promoted into noisy live Diagnostics. Positively resolved empty DataSets and other blocking findings remain actionable. + +The command panel expands once per IED only after at least one executable command row is ready. From 696ad2fa48c0faf076d9ba7dc37fca9994cc766e Mon Sep 17 00:00:00 2001 From: masarray Date: Tue, 14 Jul 2026 17:11:02 +0700 Subject: [PATCH 23/24] ci: publish validated repair onto current-main branch --- .github/workflows/apply-scl-workflow-layout-repair.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/apply-scl-workflow-layout-repair.yml b/.github/workflows/apply-scl-workflow-layout-repair.yml index 39bf9812..639491bc 100644 --- a/.github/workflows/apply-scl-workflow-layout-repair.yml +++ b/.github/workflows/apply-scl-workflow-layout-repair.yml @@ -60,7 +60,7 @@ jobs: if: steps.transform.outcome == 'success' env: GH_TOKEN: ${{ github.token }} - TARGET_BRANCH: fix/scl-workflow-layout-repair + TARGET_BRANCH: fix/scl-workflow-layout-final shell: bash run: | set -euo pipefail From 29fe13051ca735d492591378dd7cbb7f49508a41 Mon Sep 17 00:00:00 2001 From: masarray Date: Tue, 14 Jul 2026 17:11:50 +0700 Subject: [PATCH 24/24] ci: trigger final repaired-source publish --- .repair/trigger-final | 1 + 1 file changed, 1 insertion(+) create mode 100644 .repair/trigger-final diff --git a/.repair/trigger-final b/.repair/trigger-final new file mode 100644 index 00000000..08a9b401 --- /dev/null +++ b/.repair/trigger-final @@ -0,0 +1 @@ +publish validated repair to fix/scl-workflow-layout-final