Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
495d533
G2.6 enable guarded static-to-dynamic RCB recovery
masarray Aug 24, 2026
0f2bf44
G2.6 fail closed when static rollback evidence is unavailable
masarray Aug 24, 2026
bf81476
G2.6 pass proven static cleanup into dynamic recovery
masarray Aug 24, 2026
1fdae96
G2.6 regress guarded static-to-dynamic recovery
masarray Aug 24, 2026
d3d96e3
G2.6 preserve field safety around smart dynamic recovery
masarray Aug 24, 2026
bbdd864
G2.6 update P6.2B recovery safety regression
masarray Aug 24, 2026
4521dc3
P1 pin ARIEC G2.6 production consumer
masarray Aug 24, 2026
040a7d9
P1 add deterministic command-bound A3 commissioning
masarray Aug 24, 2026
716b53e
P1 add deterministic A3 evidence window
masarray Aug 24, 2026
b66783f
P1 expose deterministic A3 commissioning hotkey
masarray Aug 24, 2026
e55f5b9
P1 lock deterministic A3 safety regressions
masarray Aug 24, 2026
5f974a5
P1 document deterministic A3 field contract
masarray Aug 24, 2026
743bf10
P1 pin merged ARIEC production consumer on main
masarray Aug 24, 2026
83eea7f
P1 align engine-lock regression with merged main commit
masarray Aug 24, 2026
89567de
P1 align dchg core regression with exact contract
masarray Aug 24, 2026
a2cd472
P1 advance G1 engine-lock regression to merged G2.6 consumer
masarray Aug 24, 2026
5fb20ac
P1 align strict dchg regression with actual validator wording
masarray Aug 24, 2026
62984b3
G2.6 P1: add transactional command-focus requalification
masarray Aug 24, 2026
843ba03
G2.6 P1: wire transactional recovery before A3
masarray Aug 24, 2026
30c5dd3
G2.6 P1: lock transactional recovery invariants
masarray Aug 24, 2026
eaa198e
G2.6 P1: document transactional command-focus recovery
masarray Aug 24, 2026
2eff80e
Fix P1 recovery evidence collection build
masarray Aug 24, 2026
a04a1a8
G2.6 P1: add Q0 target-locked one-shot auto stimulus
masarray Aug 24, 2026
1f7cf7e
G2.6 P1: run Q0 auto A3 without command dialogs
masarray Aug 24, 2026
6db7b7d
G2.6 P1: lock regression contract for Q0 auto stimulus
masarray Aug 24, 2026
6ce113d
G2.6 P1: document target-locked Q0 auto A3
masarray Aug 24, 2026
fa50de3
G2.6 P1: suppress clone status without firing shared observers
masarray Aug 24, 2026
aac64af
G2.6 P1: type one-shot control task with command result
masarray Aug 24, 2026
e3e3ed0
G2.6 P1: align clone regression with fail-closed backing-field mutation
masarray Aug 24, 2026
4eedc14
G2.6 P1: align recovery regression with automatic Q0 coordinator
masarray Aug 24, 2026
0db2fef
G2.6 P1: preserve dchg report receive time for command ordering proof
masarray Aug 24, 2026
dd054b6
G2.6 P1: bind A3 PASS to accepted control and post-command report
masarray Aug 24, 2026
d9a8f83
test: lock P1 native acceptance and report ordering gates
masarray Aug 24, 2026
ab27ab2
docs: record physical A3 acceptance and final correlation hardening
masarray Aug 24, 2026
a15f701
docs: align final merge-candidate head after acceptance record
masarray Aug 24, 2026
39b8b5d
docs: freeze final P1 acceptance record
masarray Aug 24, 2026
039a2a9
docs: no-op normalization before merge
masarray Aug 24, 2026
305f82c
docs: finalize acceptance record metadata
masarray Aug 24, 2026
b3be7be
docs: finalize P1 record
masarray Aug 24, 2026
712f831
docs: preserve final P1 record
masarray Aug 24, 2026
dc920e4
docs: final acceptance record touch
masarray Aug 24, 2026
a5159b2
docs: finalize acceptance record before merge
masarray Aug 24, 2026
a763326
docs: lock final P1 record
masarray Aug 24, 2026
57cc3cd
Merge PR #231: deterministic Q0 command-bound A3 proof
masarray Aug 24, 2026
af2c8b0
Pin ARIEC PR98 shadow verification engine
masarray Aug 24, 2026
c76f191
G2.6 add fail-closed ARSAS shadow acceptance gate
masarray Aug 24, 2026
32d5dcb
G2.6 update engine lock regression for PR98
masarray Aug 24, 2026
25ebb33
Preserve G1 ancestry under ARIEC PR98 pin
masarray Aug 24, 2026
3f32f92
G2.6 test ARSAS shadow acceptance boundary
masarray Aug 24, 2026
460b912
Document ARSAS G2.6 shadow acceptance boundary
masarray Aug 24, 2026
ea1639e
G2.6 add bounded exact-member shadow evidence recorder
masarray Aug 24, 2026
4ab65d2
G2.6 test bounded exact shadow evidence recorder
masarray Aug 24, 2026
cb7e902
G2.6: pin strict ARIEC shadow production evidence policy
masarray Aug 24, 2026
660277f
G2.6: use strict observed q/t production acceptance bridge
masarray Aug 24, 2026
b870802
G2.6: update G1 lock regression for ARIEC PR99
masarray Aug 24, 2026
772f4ef
G2.6: regress strict shadow production evidence bridge
masarray Aug 24, 2026
9a7c913
G2.6: keep A3 regressions aligned with strict PR99 engine pin
masarray Aug 24, 2026
0561b78
G2.6: add two-association physical shadow commissioning collector
masarray Aug 24, 2026
8b21798
G2.6: regress physical shadow collector safety and reconnect contract
masarray Aug 24, 2026
ccbbcbb
G2.6: add physical shadow evidence result view
masarray Aug 24, 2026
db25d2f
G2.6: wire Ctrl+Shift+S physical shadow commissioning flow
masarray Aug 24, 2026
e7b6018
docs: define G2.6 physical shadow commissioning gate
masarray Aug 24, 2026
79348ad
G2.6: regress explicit shadow hotkey and production-off UX
masarray Aug 24, 2026
01da323
G2.6: fix physical shadow collector build contracts
masarray Aug 24, 2026
8f6dde0
G2.6: make no-promotion regression detect calls not documentation
masarray Aug 24, 2026
d4876cb
G2.6: read independent polling quality and timestamp companions
masarray Aug 26, 2026
e0ea040
G2.6: collect independent polling q/t evidence
masarray Aug 26, 2026
bd5e039
G2.6: regress independent polling q/t evidence
masarray Aug 26, 2026
ae1e6b6
G2.6: document independent polling q/t companions
masarray Aug 26, 2026
ef1a74e
G2.6: load InformationReportProven guarded dynamic runtime context
masarray Aug 26, 2026
1e9836e
G2.6: wire guarded Smart Dynamic RCB into normal monitoring
masarray Aug 26, 2026
b936e44
G2.6: preserve guarded context through static-to-dynamic recovery
masarray Aug 26, 2026
43118fb
G2.6: pin guarded dynamic runtime engine PR 100
masarray Aug 26, 2026
edab0af
G2.6: update Smart Auto recovery regression for guarded runtime
masarray Aug 26, 2026
b0a5ba6
G2.6: add guarded Smart Dynamic runtime regressions
masarray Aug 26, 2026
8a671a6
G2.6: document normal Smart Dynamic RCB runtime
masarray Aug 26, 2026
aad3b5a
G2.6: import ARIEC discovery contracts for guarded runtime
masarray Aug 26, 2026
aca2c45
G2.6: align regressions with guarded Smart Dynamic runtime boundary
masarray Aug 26, 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
164 changes: 126 additions & 38 deletions DynamicReportCommandBoundWitnessUiBehavior.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public static void Install()
handledEventsToo: true);

// Legacy observer-only fallback for the dedicated ControlCommandWindow path.
// V3 command authority is the already-existing runtime Diagnostic event; this
// V3/A3 command authority is the already-existing runtime Diagnostic event; this
// routed observer is retained only as non-authoritative fallback evidence.
EventManager.RegisterClassHandler(
typeof(Button),
Expand Down Expand Up @@ -57,17 +57,28 @@ private static async void OnPreviewKeyDown(object sender, KeyEventArgs e)
{
if (sender is not MainWindow window ||
Keyboard.Modifiers != (ModifierKeys.Control | ModifierKeys.Shift) ||
e.Key != Key.F)
(e.Key != Key.F && e.Key != Key.A && e.Key != Key.S))
return;

e.Handled = true;
var device = window.SelectedDevice;
var a3 = e.Key == Key.A;
var shadow = e.Key == Key.S;
var title = shadow
? "G2.6 Physical Shadow Verification"
: a3
? "G2.6-P1 Q0 Target-Locked Auto A3"
: "G2.5-A2.1 Command-Bound Witness";
if (device is null)
{
MessageBox.Show(
window,
"Select one IEC 61850 IED first. G2.5-A2.1 is intentionally bound to one explicit IED and one explicit ARSAS command.",
"G2.5-A2.1 Command-Bound Witness",
shadow
? "Select the exact identity-compatible InformationReportProven IEC 61850 IED first. G2.6 shadow is bound to the persisted proven RCB/member envelope and will not guess another target."
: a3
? "Select the qualified AA1C1F08R4 IEC 61850 IED first. Q0 Auto A3 is hard-bound to the exact proven field identity and AA1C1F08R4Q0/CSWI1.Pos."
: "Select one IEC 61850 IED first. G2.5-A2.1 is intentionally bound to one explicit IED and one explicit ARSAS command.",
title,
MessageBoxButton.OK,
MessageBoxImage.Information);
return;
Expand All @@ -77,52 +88,37 @@ private static async void OnPreviewKeyDown(object sender, KeyEventArgs e)
{
MessageBox.Show(
window,
"G2.5-A2.1 is already armed/running.",
"G2.5-A2.1 Command-Bound Witness",
"A G2 commissioning witness/shadow action is already armed or running.",
title,
MessageBoxButton.OK,
MessageBoxImage.Information);
return;
}

try
{
var answer = MessageBox.Show(
window,
$"Arm G2.5-A2.1 V3 command-bound high-speed stimulus witness for {device.Name} ({device.EndpointText})?\n\n" +
"READ-ONLY MMS WITNESS + ONE EXISTING ARSAS CONTROL COMMAND\n\n" +
"V3 captures the exact command from the ALREADY-EXISTING Iec61850MonitorRuntime Diagnostic event 'Control execution requested:' that is emitted before native control execution. It does not add a hook to the SBOw/Operate transaction.\n\n" +
"After the status shows 'G2.5-A2.1 READY — ISSUE ONE ARSAS COMMAND', issue exactly ONE already-proven safe OPEN or CLOSE using the normal ARSAS control UI you normally use. Do NOT use an external/manual stimulus for this phase.\n\n" +
"Once the runtime diagnostic identifies the exact control object, the isolated MMS witness narrows read-only sampling to at most six points around the exact ControlStatusReference. Existing ExecuteControlAsync, SBOw, Operate and CommandTermination behavior is NOT modified, delayed, wrapped or re-issued.\n\n" +
"Once 'G2.5-A2.1 COMMAND CAPTURED' appears, do not issue another command. If a transition is seen, A2.1 samples briefly to classify persistent/latched versus momentary/pulse behavior.\n\n" +
"The witness does not access/mutate RCB or DataSet state, does not send GI, does not save/advance the qualification profile, and production dynamic reporting remains OFF. Do not run another G2 hotkey while A2.1 is armed.\n\n" +
"Continue?",
"G2.5-A2.1 V3 Runtime-Diagnostic Witness",
MessageBoxButton.YesNo,
MessageBoxImage.Warning,
MessageBoxResult.No);
if (answer != MessageBoxResult.Yes)
return;

window.LastStatusText = $"G2.5-A2.1 V3: opening isolated read-only MMS witness for {device.Name} and preparing pre-command baseline…";
var progress = new Progress<string>(text => window.LastStatusText = text);
var service = new DynamicReportCommandBoundStimulusWitnessServiceV3();
var result = await service.RunAsync(
window.A21WitnessRuntime,
device,
device.Signals.ToArray(),
progress,
CancellationToken.None);
window.LastStatusText = result.Summary;
var evidenceWindow = new DynamicReportQualificationResultWindow(result) { Owner = window };
evidenceWindow.ShowDialog();
if (shadow)
await RunPhysicalShadowAsync(window, device);
else if (a3)
await RunDeterministicA3Async(window, device);
else
await RunA21Async(window, device);
}
catch (Exception ex)
{
window.LastStatusText = "G2.5-A2.1 V3 stopped locally; production dynamic reporting remains OFF.";
window.LastStatusText = shadow
? "G2.6 physical shadow stopped fail-closed. Cleanup evidence is retained; profile remains InformationReportProven and ProductionEligible remains OFF."
: a3
? "G2.6-P1 Q0 Auto A3 stopped fail-closed. No retry/CLOSE/toggle is issued; ProductionEligible remains OFF."
: "G2.5-A2.1 V3 stopped locally; production dynamic reporting remains OFF.";
MessageBox.Show(
window,
"G2.5-A2.1 V3 stopped. The witness did not change production reporting policy.\n\n" + ex,
"G2.5-A2.1 V3 Runtime-Diagnostic Witness",
(shadow
? "G2.6 physical shadow stopped. The collector issues zero automatic control commands and cannot promote the profile. Inspect cleanup/reconnect evidence before retry. Production automatic dynamic reporting remains OFF.\n\n"
: a3
? "G2.6-P1 Q0 target-locked Auto A3 stopped. Ctrl+Shift+A is the explicit commissioning action, but the one-shot OPEN is dispatched only after exact identity, Q0 status, Closed-state, command-focus, dchg-arm and final-baseline gates close. A blocked/ambiguous command is never retried and no CLOSE/toggle/auto-restore is issued. Recovery remains transactional and A3 cleanup remains mandatory. ProductionEligible stays OFF.\n\n"
: "G2.5-A2.1 V3 stopped. The witness did not change production reporting policy.\n\n") + ex,
title,
MessageBoxButton.OK,
MessageBoxImage.Error);
}
Expand All @@ -131,4 +127,96 @@ private static async void OnPreviewKeyDown(object sender, KeyEventArgs e)
Interlocked.Exchange(ref _busy, 0);
}
}

private static async Task RunA21Async(MainWindow window, Models.Iec61850MonitorDevice device)
{
var answer = MessageBox.Show(
window,
$"Arm G2.5-A2.1 V3 command-bound high-speed stimulus witness for {device.Name} ({device.EndpointText})?\n\n" +
"READ-ONLY MMS WITNESS + ONE EXISTING ARSAS CONTROL COMMAND\n\n" +
"V3 captures the exact command from the ALREADY-EXISTING Iec61850MonitorRuntime Diagnostic event 'Control execution requested:' that is emitted before native control execution. It does not add a hook to the SBOw/Operate transaction.\n\n" +
"After the status shows 'G2.5-A2.1 READY — ISSUE ONE ARSAS COMMAND', issue exactly ONE already-proven safe OPEN or CLOSE using the normal ARSAS control UI you normally use. Do NOT use an external/manual stimulus for this phase.\n\n" +
"Once the runtime diagnostic identifies the exact control object, the isolated MMS witness narrows read-only sampling to at most six points around the exact ControlStatusReference. Existing ExecuteControlAsync, SBOw, Operate and CommandTermination behavior is NOT modified, delayed, wrapped or re-issued.\n\n" +
"Once 'G2.5-A2.1 COMMAND CAPTURED' appears, do not issue another command. If a transition is seen, A2.1 samples briefly to classify persistent/latched versus momentary/pulse behavior.\n\n" +
"The witness does not access/mutate RCB or DataSet state, does not send GI, does not save/advance the qualification profile, and production dynamic reporting remains OFF. Do not run another G2 hotkey while A2.1 is armed.\n\n" +
"Continue?",
"G2.5-A2.1 V3 Runtime-Diagnostic Witness",
MessageBoxButton.YesNo,
MessageBoxImage.Warning,
MessageBoxResult.No);
if (answer != MessageBoxResult.Yes)
return;

window.LastStatusText = $"G2.5-A2.1 V3: opening isolated read-only MMS witness for {device.Name} and preparing pre-command baseline…";
var progress = new Progress<string>(text => window.LastStatusText = text);
var service = new DynamicReportCommandBoundStimulusWitnessServiceV3();
var result = await service.RunAsync(
window.A21WitnessRuntime,
device,
device.Signals.ToArray(),
progress,
CancellationToken.None);
window.LastStatusText = result.Summary;
var evidenceWindow = new DynamicReportQualificationResultWindow(result) { Owner = window };
evidenceWindow.ShowDialog();
}

private static async Task RunDeterministicA3Async(MainWindow window, Models.Iec61850MonitorDevice device)
{
// Ctrl+Shift+A itself is the explicit commissioning action. There are deliberately
// no modal arm/recovery/command dialogs in the successful path: the coordinator is
// hard-bound to the already-proven field identity and Q0 CSWI1.Pos, performs all
// read-only/transactional gates first, and dispatches one OPEN only after the A3
// final baseline is ready. Any failed gate sends zero commands.
window.LastStatusText =
$"G2.6-P1 Q0 AUTO starting for {device.Name}: exact target {DynamicReportQ0TargetLockedAutoA3CommissioningService.TargetControlReference}; one-shot OPEN only from Closed; no retry/CLOSE/toggle/auto-restore…";

var progress = new Progress<string>(text => window.LastStatusText = text);
var service = new DynamicReportQ0TargetLockedAutoA3CommissioningService();
var result = await service.RunAsync(
window.A21WitnessRuntime,
device,
device.Signals.ToArray(),
progress,
CancellationToken.None);

window.LastStatusText = result.Summary;
var evidenceWindow = new DynamicReportQualificationResultWindow(result) { Owner = window };
evidenceWindow.ShowDialog();
}

private static async Task RunPhysicalShadowAsync(MainWindow window, Models.Iec61850MonitorDevice device)
{
var answer = MessageBox.Show(
window,
$"Start G2.6 physical shadow verification for {device.Name} ({device.EndpointText})?\n\n" +
"TWO PHYSICAL PHASES + ONE DELIBERATE RECONNECT\n\n" +
"The collector will use the exact persisted InformationReportProven URCB/member envelope. Each phase opens an independent READ-ONLY MMS polling association plus one transactional dchg-only report association.\n\n" +
"When the status shows 'G2.6 SHADOW PHASE 1 READY — CAUSE ONE SAFE CHANGE', cause exactly ONE already-approved safe process/status change affecting the proven envelope. After cleanup, the collector deliberately reconnects both paths and will show a second READY marker for one more safe change.\n\n" +
"Ctrl+Shift+S issues ZERO automatic control commands. It sends no GI, never edits the persisted qualification profile, never marks ProductionEligible, and retains mandatory monitor/proof-field/fresh-association cleanup.\n\n" +
"Quality/timestamp evidence is never invented. If the scalar report envelope does not physically carry paired q/t, the strict PR #99 shadow gate will remain BLOCKED/FAIL and that field finding is intentional.\n\n" +
"Independent Smart Control/static-report regressions are NOT assumed by this action, so even a shadow PASS is not an automatic production promotion.\n\n" +
"Continue?",
"G2.6 Physical Shadow Verification",
MessageBoxButton.YesNo,
MessageBoxImage.Warning,
MessageBoxResult.No);
if (answer != MessageBoxResult.Yes)
return;

window.LastStatusText = $"G2.6 physical shadow starting for {device.Name}: validating exact profile, opening read-only polling reference and transactional dchg report path…";
var progress = new Progress<string>(text => window.LastStatusText = text);
var service = new DynamicReportShadowVerificationCommissioningService();
var result = await service.RunAsync(
device,
device.Signals.ToArray(),
progress,
controlRegressionPassed: false,
staticReportingRegressionPassed: false,
CancellationToken.None);

window.LastStatusText = result.Summary;
var evidenceWindow = new DynamicReportQualificationResultWindow(result) { Owner = window };
evidenceWindow.ShowDialog();
}
}
93 changes: 93 additions & 0 deletions DynamicReportQualificationResultWindow.G26P1A3.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using System.Text;
using ArIED61850Tester.Services;

namespace ArIED61850Tester;

internal partial class DynamicReportQualificationResultWindow
{
internal DynamicReportQualificationResultWindow(DynamicReportCommandBoundA3CommissioningResult result)
{
ArgumentNullException.ThrowIfNull(result);
InitializeComponent();

Title = "G2.6-P1 Deterministic A3 dchg Proof Evidence";
HeaderText.Text = "G2.6-P1 Deterministic A3 — Command → dchg Report";
SummaryText.Text = result.Summary;
StateText.Text = result.IsSuccess
? "A3 Command-Bound dchg Proven"
: result.IsBlocked
? "Blocked"
: "A3 Not Proven";
EvidenceTextBox.Text = BuildG26P1A3Evidence(result);

if (result.IsSuccess)
SetPassBadge();
}

private static string BuildG26P1A3Evidence(DynamicReportCommandBoundA3CommissioningResult result)
{
var builder = new StringBuilder();
builder.AppendLine("ARSAS G2.6-P1 DETERMINISTIC A3 COMMAND-BOUND DCHG EVIDENCE");
builder.AppendLine(new string('=', 76));
builder.AppendLine($"Result: {result.Summary}");
builder.AppendLine($"Blocked: {result.IsBlocked}");
builder.AppendLine($"A3 success: {result.IsSuccess}");
builder.AppendLine($"Command/report correlation: {result.CommandBoundReportCorrelationProven}");

builder.AppendLine();
builder.AppendLine("EXISTING ARSAS COMMAND");
builder.AppendLine($"Captured: {result.Witness.CommandCaptured}");
builder.AppendLine($"Object: {TextOrDash(result.Witness.CommandSignalReference)}");
builder.AppendLine($"Control status: {TextOrDash(result.Witness.ControlStatusReference)}");
builder.AppendLine($"Requested value: {TextOrDash(result.Witness.RequestedValue)}");
builder.AppendLine($"Source: {TextOrDash(result.Witness.CommandSource)}");
builder.AppendLine($"Observed at UTC: {result.Witness.CommandObservedAtUtc?.ToString("O") ?? "-"}");
builder.AppendLine($"Command-bound transition proven: {result.Witness.CommandBoundTransitionProven}");
builder.AppendLine($"Read-only witness association healthy: {result.Witness.AssociationHealthy}");
builder.AppendLine($"Witness cycles/read failures: {result.Witness.SampleCycles}/{result.Witness.ReadFailures}");

if (result.Witness.Transitions.Count > 0)
{
builder.AppendLine();
builder.AppendLine("COMMAND-BOUND QUALIFIED-MEMBER TRANSITIONS");
foreach (var transition in result.Witness.Transitions)
{
builder.AppendLine(
$"[{transition.Index}] {transition.MemberReference} ({transition.PointReference}) {transition.BeforeValue} -> {transition.AfterValue} at {transition.ObservedAtUtc:O}");
}
}

builder.AppendLine();
builder.AppendLine("DCHG INFORMATIONREPORT");
builder.AppendLine($"Core success: {result.CoreResult.IsSuccess}");
builder.AppendLine($"Activation proven: {result.CoreResult.ActivationProven}");
builder.AppendLine($"Spontaneous dchg proven: {result.CoreResult.SpontaneousDataChangeProven}");
builder.AppendLine($"URCB: {TextOrDash(result.CoreResult.RcbReference)}");
builder.AppendLine($"Temporary DataSet: {TextOrDash(result.CoreResult.DataSetReference)}");
builder.AppendLine($"RptID: {TextOrDash(result.CoreResult.ReportId)}");
builder.AppendLine($"Report included indexes: [{string.Join(",", result.CoreResult.IncludedIndexes)}]");
builder.AppendLine($"Report reasons: [{string.Join(",", result.CoreResult.Reasons)}]");
builder.AppendLine($"Correlated command/report indexes: [{string.Join(",", result.CorrelatedIndexes)}]");
foreach (var member in result.CorrelatedMemberReferences)
builder.AppendLine("- correlated member: " + member);

builder.AppendLine();
builder.AppendLine("CLEANUP / RELEASE");
builder.AppendLine($"Monitor cleanup: {result.CoreResult.MonitorCleanupSucceeded}");
builder.AppendLine($"TrgOps/OptFlds restore: {result.CoreResult.ProofFieldRestoreSucceeded}");
builder.AppendLine($"Fresh-association cleanup closure: {result.CoreResult.FreshCleanupClosureSucceeded}");
builder.AppendLine($"Report association healthy after proof: {result.CoreResult.AssociationHealthyAfterReport}");

builder.AppendLine();
builder.AppendLine("FULL EVIDENCE");
foreach (var line in result.EvidenceLines)
builder.AppendLine(line);

builder.AppendLine();
builder.AppendLine("SAFETY STATE");
builder.AppendLine("A3 command-bound dchg PASS != ProductionEligible.");
builder.AppendLine("This commissioning action does not save or advance the persisted profile.");
builder.AppendLine("Production automatic dynamic reporting remains OFF until later shadow verification and G2.6 regression acceptance explicitly mark the identity ProductionEligible.");
return builder.ToString();
}
}
Loading
Loading