fix: finalize script-driven limited Apply evidence
This commit is contained in:
@@ -60,7 +60,7 @@ public sealed class EntryPoint : IExtensionApplication
|
|||||||
public void AutomationAfterPxp2Command() => ScriptDrivenFirstApplyAutomation.RecordSentinel("AfterPxp2");
|
public void AutomationAfterPxp2Command() => ScriptDrivenFirstApplyAutomation.RecordSentinel("AfterPxp2");
|
||||||
|
|
||||||
[CommandMethod("PHP_REC_AUTOMATION_FINALIZE", CommandFlags.Session)]
|
[CommandMethod("PHP_REC_AUTOMATION_FINALIZE", CommandFlags.Session)]
|
||||||
public void AutomationFinalizeCommand() => ScriptDrivenFirstApplyAutomation.FinalizePreview();
|
public void AutomationFinalizeCommand() => ScriptDrivenFirstApplyAutomation.FinalizeAutomation();
|
||||||
|
|
||||||
private static void RunDiagnostic(string commandName)
|
private static void RunDiagnostic(string commandName)
|
||||||
{
|
{
|
||||||
@@ -201,6 +201,7 @@ public sealed class EntryPoint : IExtensionApplication
|
|||||||
RuntimeDiagnostics.Write("ApplyPreflightCompleted", operationId: operationId, commandName: commandName, resultStatus: "Accepted", document: document);
|
RuntimeDiagnostics.Write("ApplyPreflightCompleted", operationId: operationId, commandName: commandName, resultStatus: "Accepted", document: document);
|
||||||
RuntimeDiagnostics.Write("AnnotationTransactionStarted", operationId: operationId, commandName: commandName, document: document);
|
RuntimeDiagnostics.Write("AnnotationTransactionStarted", operationId: operationId, commandName: commandName, document: document);
|
||||||
var outcome = new FirstLimitedPipeAnnotationWriter().Write(document!, preflight.Operation, applyPermit.VisibilityState);
|
var outcome = new FirstLimitedPipeAnnotationWriter().Write(document!, preflight.Operation, applyPermit.VisibilityState);
|
||||||
|
ScriptDrivenFirstApplyAutomation.RecordApplyOutcome(outcome);
|
||||||
FirstLimitedApplyAutomationCoordinator.RecordApplyOutcome(outcome);
|
FirstLimitedApplyAutomationCoordinator.RecordApplyOutcome(outcome);
|
||||||
details["ApplyOutcome"] = outcome.ToString();
|
details["ApplyOutcome"] = outcome.ToString();
|
||||||
details["AnnotationsCreated"] = outcome == PipeAnnotationWriteOutcome.Created ? 1 : 0;
|
details["AnnotationsCreated"] = outcome == PipeAnnotationWriteOutcome.Created ? 1 : 0;
|
||||||
|
|||||||
+27
-8
@@ -15,7 +15,7 @@ namespace PRecAnnot.Plant3D2026;
|
|||||||
internal static class ScriptDrivenFirstApplyAutomation
|
internal static class ScriptDrivenFirstApplyAutomation
|
||||||
{
|
{
|
||||||
private sealed record Config(string Phase, string OrthoDrawingPath, string EvidencePath, string FixtureMarkerPath, string ExpectedEntryAssemblySha256, bool Diagnostic);
|
private sealed record Config(string Phase, string OrthoDrawingPath, string EvidencePath, string FixtureMarkerPath, string ExpectedEntryAssemblySha256, bool Diagnostic);
|
||||||
private sealed record Run(int Sequence, string? PlanId, IReadOnlyList<string> Candidates, string? View, int Create, int Skip, int? DbmodBefore, int? DbmodAfter, AutomationEntityCounts? Before, AutomationEntityCounts? After, bool WillStart, bool Ended, bool Failed, bool Cancelled, int? SourceRows, int? MembershipResolved);
|
private sealed record Run(int Sequence, string? PlanId, IReadOnlyList<string> Candidates, string? View, int Create, int Skip, bool? PermitAccepted, string? ApplyOutcome, int? DbmodBefore, int? DbmodAfter, AutomationEntityCounts? Before, AutomationEntityCounts? After, bool WillStart, bool Ended, bool Failed, bool Cancelled, int? SourceRows, int? MembershipResolved);
|
||||||
private static Config? config;
|
private static Config? config;
|
||||||
private static Document? fixture;
|
private static Document? fixture;
|
||||||
private static Database? database;
|
private static Database? database;
|
||||||
@@ -90,7 +90,14 @@ internal static class ScriptDrivenFirstApplyAutomation
|
|||||||
if (!prepared || terminal || runs.Count == 0) return;
|
if (!prepared || terminal || runs.Count == 0) return;
|
||||||
var last = runs[^1];
|
var last = runs[^1];
|
||||||
if (!last.WillStart || last.PlanId is not null) return;
|
if (!last.WillStart || last.PlanId is not null) return;
|
||||||
runs[^1] = last with { PlanId = planId, Candidates = candidates, View = view, Create = create, Skip = skip, SourceRows = sourceRows, MembershipResolved = membershipResolved };
|
runs[^1] = last with { PlanId = planId, Candidates = candidates, View = view, Create = create, Skip = skip, PermitAccepted = permitAccepted, SourceRows = sourceRows, MembershipResolved = membershipResolved };
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void RecordApplyOutcome(PipeAnnotationWriteOutcome outcome)
|
||||||
|
{
|
||||||
|
if (!prepared || terminal || runs.Count == 0) return;
|
||||||
|
var last = runs[^1];
|
||||||
|
runs[^1] = last with { ApplyOutcome = outcome.ToString() };
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static bool TryGetPreparedContext(Document? commandDocument, out PlantRuntimeContext context)
|
internal static bool TryGetPreparedContext(Document? commandDocument, out PlantRuntimeContext context)
|
||||||
@@ -105,19 +112,29 @@ internal static class ScriptDrivenFirstApplyAutomation
|
|||||||
|
|
||||||
internal static string? SelectedTargetSnapshotId => prepared && !terminal ? selectedTargetSnapshotId : null;
|
internal static string? SelectedTargetSnapshotId => prepared && !terminal ? selectedTargetSnapshotId : null;
|
||||||
|
|
||||||
internal static void FinalizePreview()
|
internal static void FinalizeAutomation()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!prepared) throw new InvalidOperationException("AUTOMATION_NOT_PREPARED");
|
if (!prepared) throw new InvalidOperationException("AUTOMATION_NOT_PREPARED");
|
||||||
if (runs.Count != 2) throw new InvalidOperationException("AUTOMATION_EXPECTS_EXACTLY_TWO_PXP_OA");
|
if (runs.Count != 2) throw new InvalidOperationException("AUTOMATION_EXPECTS_EXACTLY_TWO_PXP_OA");
|
||||||
if (runs.Any(x => !x.WillStart || !x.Ended || x.Failed || x.Cancelled)) throw new InvalidOperationException("AUTOMATION_PXP_OA_NOT_ENDED");
|
if (runs.Any(x => !x.WillStart || !x.Ended || x.Failed || x.Cancelled)) throw new InvalidOperationException("AUTOMATION_PXP_OA_NOT_ENDED");
|
||||||
if (runs.Any(x => string.IsNullOrWhiteSpace(x.PlanId)) || !string.Equals(runs[0].PlanId, runs[1].PlanId, StringComparison.Ordinal)) throw new InvalidOperationException("AUTOMATION_PLAN_IDS_DIFFER");
|
if (runs.Any(x => string.IsNullOrWhiteSpace(x.PlanId))) throw new InvalidOperationException("PXP_OA_PLAN_RESULT_NOT_CAPTURED");
|
||||||
|
if (!string.Equals(runs[0].PlanId, runs[1].PlanId, StringComparison.Ordinal)) throw new InvalidOperationException("AUTOMATION_PLAN_IDS_DIFFER");
|
||||||
if (runs.Any(x => x.Create != 1)) throw new InvalidOperationException("AUTOMATION_PREVIEW_CREATE_NOT_ONE");
|
if (runs.Any(x => x.Create != 1)) throw new InvalidOperationException("AUTOMATION_PREVIEW_CREATE_NOT_ONE");
|
||||||
if (runs[0].Candidates.Count == 0) throw new InvalidOperationException("AUTOMATION_NO_ELIGIBLE_CANDIDATE");
|
if (runs[0].Candidates.Count == 0) throw new InvalidOperationException("AUTOMATION_NO_ELIGIBLE_CANDIDATE");
|
||||||
if (Dbmod() != dbmodBaseline || !Equals(Counts(database!), baseline)) throw new InvalidOperationException("AUTOMATION_INSPECTOR_INVARIANCE_FAILED");
|
var apply = string.Equals(config!.Phase, "Apply", StringComparison.OrdinalIgnoreCase);
|
||||||
|
if (apply)
|
||||||
|
{
|
||||||
|
if (runs[0].PermitAccepted != true) throw new InvalidOperationException("AUTOMATION_APPLY_PERMIT_NOT_ACCEPTED");
|
||||||
|
if (!string.Equals(runs[0].ApplyOutcome, PipeAnnotationWriteOutcome.Created.ToString(), StringComparison.Ordinal)) throw new InvalidOperationException("AUTOMATION_APPLY_CREATE_NOT_CONFIRMED");
|
||||||
|
if (!string.Equals(runs[1].ApplyOutcome, PipeAnnotationWriteOutcome.AlreadyExists.ToString(), StringComparison.Ordinal)) throw new InvalidOperationException("AUTOMATION_APPLY_DUPLICATE_NOT_CONFIRMED");
|
||||||
|
var undoCounts = Counts(database!);
|
||||||
|
if (!Equals(undoCounts, baseline)) throw new InvalidOperationException("AUTOMATION_UNDO_DID_NOT_RESTORE_BASELINE");
|
||||||
|
}
|
||||||
|
else if (Dbmod() != dbmodBaseline || !Equals(Counts(database!), baseline)) throw new InvalidOperationException("AUTOMATION_INSPECTOR_INVARIANCE_FAILED");
|
||||||
terminal = true;
|
terminal = true;
|
||||||
transitions.Add("PreviewCompleted");
|
transitions.Add(apply ? "ApplyCompleted" : "PreviewCompleted");
|
||||||
Write(true, null);
|
Write(true, null);
|
||||||
Unsubscribe();
|
Unsubscribe();
|
||||||
}
|
}
|
||||||
@@ -132,7 +149,7 @@ internal static class ScriptDrivenFirstApplyAutomation
|
|||||||
var correlation = Correlate("WillStart", sender, args.GlobalCommandName, runs.Count < 2);
|
var correlation = Correlate("WillStart", sender, args.GlobalCommandName, runs.Count < 2);
|
||||||
correlations.Add(correlation);
|
correlations.Add(correlation);
|
||||||
if (correlation.RejectionReason is not null) return;
|
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));
|
runs.Add(new Run(runs.Count + 1, null, [], null, 0, 0, null, null, Dbmod(), null, Counts(database!), null, true, false, false, false, null, null));
|
||||||
transitions.Add("PxpOaWillStart:" + runs.Count);
|
transitions.Add("PxpOaWillStart:" + runs.Count);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -203,7 +220,9 @@ internal static class ScriptDrivenFirstApplyAutomation
|
|||||||
{
|
{
|
||||||
if (config is null) return;
|
if (config is null) return;
|
||||||
var first = runs.ElementAtOrDefault(0); var second = runs.ElementAtOrDefault(1);
|
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, CommandCorrelations = correlations };
|
var apply = string.Equals(config.Phase, "Apply", StringComparison.OrdinalIgnoreCase);
|
||||||
|
var afterUndo = apply && terminal ? Counts(database!) : null;
|
||||||
|
var evidence = new FirstApplyAutomationEvidence { Phase = config.Phase, Completed = completed, Prepared = prepared, FunctionalEvidenceCompleted = completed, ErrorCode = error, DbmodBefore = dbmodBaseline, DbmodAfterCommand1 = first?.DbmodAfter, DbmodAfterCommand2 = second?.DbmodAfter, DbmodAfterUndo = apply && terminal ? Dbmod() : null, Baseline = baseline, AfterCommand1 = first?.After, AfterCommand2 = second?.After, AfterUndo = afterUndo, CompletedCommands = runs.Where(x => x.Ended).Select(x => "PXP-OA").Concat(apply && commandEvents.Any(x => string.Equals(x, "Ended:UNDO", StringComparison.OrdinalIgnoreCase)) ? ["UNDO"] : []).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, ApplyOutcomeRun1 = first?.ApplyOutcome, ApplyOutcomeRun2 = second?.ApplyOutcome, PermitAccepted = first?.PermitAccepted, CommandsCompleted = runs.Count == 2 && runs.All(x => x.Ended) && (!apply || commandEvents.Any(x => string.Equals(x, "Ended:UNDO", StringComparison.OrdinalIgnoreCase))), InspectorsCompleted = baseline is not null && first?.After is not null && second?.After is not null && (!apply || afterUndo 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)!);
|
Directory.CreateDirectory(Path.GetDirectoryName(config.EvidencePath)!);
|
||||||
var temp = config.EvidencePath + ".tmp"; File.WriteAllText(temp, JsonSerializer.Serialize(evidence)); File.Move(temp, config.EvidencePath, true);
|
var temp = config.EvidencePath + ".tmp"; File.WriteAllText(temp, JsonSerializer.Serialize(evidence)); File.Move(temp, config.EvidencePath, true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ public sealed class PxpOaRegistrationTests
|
|||||||
[Fact] public void ScriptDrivenPxpOa_BindsPlantContextToCommandDocument() => Assert.Contains("ReadCurrentContext(document)", Entry);
|
[Fact] public void ScriptDrivenPxpOa_BindsPlantContextToCommandDocument() => Assert.Contains("ReadCurrentContext(document)", Entry);
|
||||||
[Fact] public void ScriptDrivenPxpOa_UsesPreparedFixtureContextBeforeImplicitLookup() => Assert.Contains("TryGetPreparedContext(document, out var scriptContext)", Entry);
|
[Fact] public void ScriptDrivenPxpOa_UsesPreparedFixtureContextBeforeImplicitLookup() => Assert.Contains("TryGetPreparedContext(document, out var scriptContext)", Entry);
|
||||||
[Fact] public void ScriptDrivenPrepare_SelectsCanonicalTargetAndRetainsReadOnlyContext() { Assert.Contains("preparedContext = context", Observer); Assert.Contains("selectedTargetSnapshotId = candidates[0]", Observer); }
|
[Fact] public void ScriptDrivenPrepare_SelectsCanonicalTargetAndRetainsReadOnlyContext() { Assert.Contains("preparedContext = context", Observer); Assert.Contains("selectedTargetSnapshotId = candidates[0]", Observer); }
|
||||||
|
[Fact] public void ScriptDrivenApply_RecordsOutcomeAgainstTheAcceptedRun() { Assert.Contains("internal static void RecordApplyOutcome", Observer); Assert.Contains("ApplyOutcome = outcome.ToString()", Observer); }
|
||||||
|
[Fact] public void ScriptDrivenApply_RequiresActualUndoBaselineRestoration() { Assert.Contains("AUTOMATION_UNDO_DID_NOT_RESTORE_BASELINE", Observer); Assert.Contains("Ended:UNDO", Observer); }
|
||||||
[Fact] public void PlacementReadFailure_IsDiagnosedInsteadOfBeingSilentlyDiscarded() { var source = File.ReadAllText(Path.Combine(ProjectRoot, "PlantReadPath.cs")); Assert.Contains("OrthoPipePlacementReadFailed", source); Assert.Contains("ORTHO_PIPE_PLACEMENT_UNAVAILABLE", source); }
|
[Fact] public void PlacementReadFailure_IsDiagnosedInsteadOfBeingSilentlyDiscarded() { var source = File.ReadAllText(Path.Combine(ProjectRoot, "PlantReadPath.cs")); Assert.Contains("OrthoPipePlacementReadFailed", source); Assert.Contains("ORTHO_PIPE_PLACEMENT_UNAVAILABLE", source); }
|
||||||
[Fact] public void PlacementReader_OpensSourceDatabaseWhenItIsNotActive() { var source = File.ReadAllText(Path.Combine(ProjectRoot, "PlantReadPath.cs")); Assert.Contains("sourceId.GetDatabase(true, ref openedByQuery)", source); }
|
[Fact] public void PlacementReader_OpensSourceDatabaseWhenItIsNotActive() { var source = File.ReadAllText(Path.Combine(ProjectRoot, "PlantReadPath.cs")); Assert.Contains("sourceId.GetDatabase(true, ref openedByQuery)", source); }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user