feat: add first limited Pipe Apply preflight

This commit is contained in:
codex
2026-08-03 10:37:37 +03:00
parent 209b8b89b8
commit 1bfb91bc4b
3 changed files with 122 additions and 0 deletions
@@ -183,6 +183,30 @@ public interface IAnnotationPlanBuilder { AnnotationPlan Build(AnnotationPlannin
public enum PRecAnnotExecutionMode { Smoke, DryRun, Apply }
public interface IRuntimeWriteGuard { void EnsureWriteAllowed(string operation); }
public sealed record CreatePipeAnnotationOperation
{
public required string OperationId { get; init; }
public required string SourceObjectStableKey { get; init; }
public required string ViewStableKey { get; init; }
public required string RuleId { get; init; }
public required AnnotationPoint Placement { get; init; }
public required string DuplicateKey { get; init; }
}
public sealed record FirstLimitedApplyPermit
{
public required string FixtureMarker { get; init; }
public required bool AllowApply { get; init; }
public required string TargetSnapshotId { get; init; }
public required string ExpectedPlanId { get; init; }
}
public sealed record FirstLimitedApplyPreflight
{
public required string PlanId { get; init; }
public required CreatePipeAnnotationOperation Operation { get; init; }
}
public sealed record OrthoViewportRef(string DrawingId, string ViewportId);
public sealed record PlantObjectData
@@ -0,0 +1,52 @@
using System.Security.Cryptography;
using System.Text;
using PRecAnnot.Abstractions;
namespace PRecAnnot.Core;
public sealed class FirstLimitedApplyPlanner
{
public const string FixtureMarker = "FIRST_LIMITED_APPLY";
public const string RuleId = "PXP-OA.PIPE.OPEN-END.V1";
public FirstLimitedApplyPreflight Prepare(
PRecAnnotExecutionMode mode,
AnnotationPlan plan,
OrthoViewportInfo viewport,
IReadOnlyList<PlantObjectSnapshot> objects,
FirstLimitedApplyPermit permit)
{
if (mode != PRecAnnotExecutionMode.Apply) throw new InvalidOperationException("APPLY_MODE_REQUIRED");
if (!permit.AllowApply || !string.Equals(permit.FixtureMarker, FixtureMarker, StringComparison.Ordinal))
throw new InvalidOperationException("APPLY_TEST_FIXTURE_OR_PERMIT_REQUIRED");
if (!string.Equals(permit.ExpectedPlanId, plan.PlanId, StringComparison.Ordinal))
throw new InvalidOperationException("APPLY_PLAN_STALE");
var targets = objects.Where(x => string.Equals(x.SnapshotId, permit.TargetSnapshotId, StringComparison.Ordinal)).ToArray();
if (targets.Length != 1) throw new InvalidOperationException("APPLY_REQUIRES_EXACTLY_ONE_TARGET");
var target = targets[0];
if (target.MembershipStatus != ViewMembershipStatus.Resolved) throw new InvalidOperationException("APPLY_REQUIRES_RESOLVED_MEMBERSHIP");
if (!string.Equals(target.ObjectClass, "Pipe", StringComparison.OrdinalIgnoreCase)) throw new InvalidOperationException("APPLY_UNSUPPORTED_OBJECT_CLASS");
if (target.Geometry.PaperPosition is not { } placement) throw new InvalidOperationException("ANNOTATION_PLACEMENT_NOT_RESOLVED");
var create = plan.Operations.Where(x => x.Kind == AnnotationOperationKind.Create && string.Equals(x.SourceSnapshotId, target.SnapshotId, StringComparison.Ordinal)).ToArray();
if (create.Length != 1) throw new InvalidOperationException("APPLY_TARGET_CREATE_NOT_PLANNED");
var duplicateKey = Stable("duplicate", RuleId, viewport.Id, target.SnapshotId);
return new FirstLimitedApplyPreflight
{
PlanId = plan.PlanId,
Operation = new CreatePipeAnnotationOperation
{
OperationId = Stable("operation", RuleId, viewport.Id, target.SnapshotId),
SourceObjectStableKey = target.SnapshotId,
ViewStableKey = viewport.Id,
RuleId = RuleId,
Placement = new AnnotationPoint(placement.X, placement.Y),
DuplicateKey = duplicateKey
}
};
}
private static string Stable(params string[] values) => Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(string.Join("|", values))))[..24];
}