fix: correlate script-driven PXP-OA command events

This commit is contained in:
codex
2026-08-04 14:09:14 +03:00
parent c270a182ce
commit 8a9f95a605
3 changed files with 33 additions and 6 deletions
@@ -24,6 +24,7 @@ internal static class ScriptDrivenFirstApplyAutomation
private static readonly List<string> transitions = [];
private static readonly List<string> commandEvents = [];
private static readonly List<AutomationSentinelEvidence> sentinels = [];
private static readonly List<AutomationCommandCorrelationEvidence> correlations = [];
private static PxpOaDiscoveryEvidence? discovery;
private static bool prepared;
private static bool terminal;
@@ -104,7 +105,9 @@ internal static class ScriptDrivenFirstApplyAutomation
Guard(() =>
{
commandEvents.Add("WillStart:" + args.GlobalCommandName);
if (!IsFixture(sender) || !IsPxp(args.GlobalCommandName) || runs.Count >= 2) return;
var correlation = Correlate("WillStart", sender, args.GlobalCommandName, runs.Count < 2);
correlations.Add(correlation);
if (correlation.RejectionReason is not null) return;
runs.Add(new Run(runs.Count + 1, null, [], null, 0, 0, Dbmod(), null, Counts(database!), null, true, false, false, false, null, null));
transitions.Add("PxpOaWillStart:" + runs.Count);
});
@@ -116,8 +119,11 @@ internal static class ScriptDrivenFirstApplyAutomation
{
Guard(() =>
{
commandEvents.Add((failed ? "Failed:" : cancelled ? "Cancelled:" : "Ended:") + args.GlobalCommandName);
if (!IsFixture(sender) || !IsPxp(args.GlobalCommandName) || runs.Count == 0) return;
var kind = failed ? "Failed" : cancelled ? "Cancelled" : "Ended";
commandEvents.Add(kind + ":" + args.GlobalCommandName);
var correlation = Correlate(kind, sender, args.GlobalCommandName, runs.Count > 0);
correlations.Add(correlation);
if (correlation.RejectionReason is not null) return;
var last = runs[^1];
if (last.Ended || last.Failed || last.Cancelled) return;
runs[^1] = last with { Ended = !failed && !cancelled, Failed = failed, Cancelled = cancelled, DbmodAfter = Dbmod(), After = Counts(database!) };
@@ -125,8 +131,22 @@ internal static class ScriptDrivenFirstApplyAutomation
Write(false, null);
});
}
private static bool IsFixture(object sender) => !terminal && fixture is not null && ReferenceEquals(sender, fixture) && database is not null && ReferenceEquals(fixture.Database, database);
private static bool IsPxp(string name) => string.Equals(name.TrimStart('.', '_'), "PXP-OA", StringComparison.OrdinalIgnoreCase);
private static AutomationCommandCorrelationEvidence Correlate(string kind, object sender, string rawName, bool sequenceAccepted)
{
var normalized = rawName.Trim();
var command = string.Equals(normalized, "PXP-OA", StringComparison.OrdinalIgnoreCase);
var state = prepared && !terminal && config is not null && fixture is not null && database is not null;
var senderDocument = sender as Document;
var senderPath = senderDocument?.Name;
var expectedPath = config?.OrthoDrawingPath;
var activePath = AcadApplication.DocumentManager.MdiActiveDocument?.Name;
var fixturePath = senderPath is not null && expectedPath is not null && SamePath(senderPath, expectedPath);
var activeFixture = activePath is not null && expectedPath is not null && SamePath(activePath, expectedPath);
var document = fixturePath && activeFixture;
var databaseMatch = senderDocument?.Database is not null && database is not null && SamePath(senderDocument.Database.Filename, database.Filename);
var rejection = !command ? "COMMAND_NAME_MISMATCH" : !state ? "NOT_PREPARED" : !document ? "DOCUMENT_IDENTITY_MISMATCH" : !databaseMatch ? "DATABASE_IDENTITY_MISMATCH" : !sequenceAccepted ? "UNEXPECTED_SEQUENCE" : null;
return new(kind, rawName, normalized, command, state, document, databaseMatch, fixturePath, sequenceAccepted, rejection, sender.GetType().FullName, senderPath, expectedPath, activePath, runs.Count, DateTimeOffset.UtcNow);
}
private static void Guard(Action action) { try { if (!terminal) action(); } catch (System.Exception ex) { Fail("AUTOMATION_EVENT_HANDLER_FAILED", ex); } }
private static void Fail(string code, System.Exception exception) { if (terminal) return; terminal = true; transitions.Add("Failed:" + code); try { Write(false, code + ":" + exception); } finally { Unsubscribe(); } }
private static void Unsubscribe() { if (fixture is null) return; try { fixture.CommandWillStart -= OnWillStart; fixture.CommandEnded -= OnEnded; fixture.CommandFailed -= OnFailed; fixture.CommandCancelled -= OnCancelled; } catch { } }
@@ -154,7 +174,7 @@ internal static class ScriptDrivenFirstApplyAutomation
{
if (config is null) return;
var first = runs.ElementAtOrDefault(0); var second = runs.ElementAtOrDefault(1);
var evidence = new FirstApplyAutomationEvidence { Phase = config.Phase, Completed = completed, Prepared = prepared, FunctionalEvidenceCompleted = completed, ErrorCode = error, DbmodBefore = dbmodBaseline, DbmodAfterCommand1 = first?.DbmodAfter, DbmodAfterCommand2 = second?.DbmodAfter, Baseline = baseline, AfterCommand1 = first?.After, AfterCommand2 = second?.After, CompletedCommands = runs.Where(x => x.Ended).Select(x => "PXP-OA").ToArray(), PlanIdRun1 = first?.PlanId, PlanIdRun2 = second?.PlanId, PlanIdsEqual = first is not null && second is not null ? string.Equals(first.PlanId, second.PlanId, StringComparison.Ordinal) : null, EligibleCandidateCount = first?.Candidates.Count, SelectedCandidateHash = first?.Candidates.OrderBy(x => x, StringComparer.Ordinal).FirstOrDefault(), ViewHash = first?.View, CreateRun1 = first?.Create, CreateRun2 = second?.Create, SkipRun2 = second?.Skip, CommandsCompleted = runs.Count == 2 && runs.All(x => x.Ended), InspectorsCompleted = baseline is not null && first?.After is not null && second?.After is not null, EvidenceFlushed = true, CurrentState = terminal ? (completed ? "Completed" : "Failed") : "Prepared", StateTransitions = transitions, PxpOaDiscovery = discovery, Sentinels = sentinels, CommandEvents = commandEvents };
var evidence = new FirstApplyAutomationEvidence { Phase = config.Phase, Completed = completed, Prepared = prepared, FunctionalEvidenceCompleted = completed, ErrorCode = error, DbmodBefore = dbmodBaseline, DbmodAfterCommand1 = first?.DbmodAfter, DbmodAfterCommand2 = second?.DbmodAfter, Baseline = baseline, AfterCommand1 = first?.After, AfterCommand2 = second?.After, CompletedCommands = runs.Where(x => x.Ended).Select(x => "PXP-OA").ToArray(), PlanIdRun1 = first?.PlanId, PlanIdRun2 = second?.PlanId, PlanIdsEqual = first is not null && second is not null ? string.Equals(first.PlanId, second.PlanId, StringComparison.Ordinal) : null, EligibleCandidateCount = first?.Candidates.Count, SelectedCandidateHash = first?.Candidates.OrderBy(x => x, StringComparer.Ordinal).FirstOrDefault(), ViewHash = first?.View, CreateRun1 = first?.Create, CreateRun2 = second?.Create, SkipRun2 = second?.Skip, CommandsCompleted = runs.Count == 2 && runs.All(x => x.Ended), InspectorsCompleted = baseline is not null && first?.After is not null && second?.After is not null, EvidenceFlushed = true, CurrentState = terminal ? (completed ? "Completed" : "Failed") : "Prepared", StateTransitions = transitions, PxpOaDiscovery = discovery, Sentinels = sentinels, CommandEvents = commandEvents, CommandCorrelations = correlations };
Directory.CreateDirectory(Path.GetDirectoryName(config.EvidencePath)!);
var temp = config.EvidencePath + ".tmp"; File.WriteAllText(temp, JsonSerializer.Serialize(evidence)); File.Move(temp, config.EvidencePath, true);
}