Compare commits

..
14 changed files with 1094 additions and 27 deletions
+34
View File
@@ -127,3 +127,37 @@ formatting в одном commit.
runtime-сценарий; записаны фактические результаты; выполнен rollback; fixture и
production изолированы; механика документирована; ветка опубликована обычным
push.
## Общая карта проекта
Каноническая общая карта проекта находится в Google Drive:
`https://drive.google.com/drive/folders/1avnR0V8q7y5pfWRYwB6LPBEEyfpN7PpV`.
Локальная синхронизированная копия, если она доступна, определяется только
переменной среды `PHIM_PLANT3D_PROJECT_MAP_ROOT`; её путь не хардкодить.
Перед работой над архитектурой, миграцией или runtime-сценарием обязательно
прочитать доступные индекс/обзор карты, целевую архитектуру и migration status,
индекс runtime evidence, а также записи карты для затрагиваемых Launcher и
P_Rec_Annot. Затем сверить их с локальными AGENTS.md, релевантными tracked
документами, текущим кодом и тестами.
Источники истины имеют следующий приоритет: фактическое runtime evidence;
текущий код, tests и tracked configuration на проверенном Git HEAD; tracked
документация репозитория; общая карта проекта. Внешняя карта описывает и
связывает работу, но не является build/runtime dependency и не переопределяет
текущий код или runtime evidence.
Проверять свежесть каждой используемой записи карты по фактическим `git
rev-parse HEAD`, ветке и привязанному runtime evidence. Если карта или её
локальная копия недоступна, фиксировать `PROJECT_MAP_UNAVAILABLE`; если
зафиксированные commit/runtime status не совпадают с фактическими —
`PROJECT_MAP_STALE`. Эти статусы не разрешают выдумывать evidence: продолжать
по доступным локальным источникам и явно сообщать расхождение.
Системные инварианты сохраняются: Launcher доставляет versioned local package
с одним managed entry assembly и без Autodesk DLL; P_Rec_Annot подтверждает
реальное Plant/Ortho/DataLinks поведение runtime evidence, а не только build
или tests. После изменения архитектуры либо runtime-статуса в отчёте указывать
конкретные документы общей карты, которые требуют обновления (архитектура,
статус миграции, evidence index и запись затронутой подсистемы); не заявлять,
что они уже обновлены, без фактического изменения.
+30
View File
@@ -87,3 +87,33 @@ Autodesk properties по строковым именам.
одного закрытого состояния проекта. Сравнивать нужно `control delta` с
`DryRun delta`: одинаковые изменения классифицируются как baseline behavior
Plant 3D, а plugin-only изменения блокируют дальнейшую работу.
## Общая карта проекта
Каноническая общая карта проекта:
`https://drive.google.com/drive/folders/1avnR0V8q7y5pfWRYwB6LPBEEyfpN7PpV`.
Необязательная локальная копия доступна только через
`PHIM_PLANT3D_PROJECT_MAP_ROOT`; локальный путь синхронизации не хардкодить.
До изменения P_Rec_Annot читать доступные индекс карты, запись архитектуры и
статуса P_Rec_Annot, DryRun/Apply contract, карту Ortho/DataLinks и связанный
runtime evidence index. Сверять их с фактическим Git HEAD, текущими plugin
code/tests/configuration и runtime evidence конкретной fixture.
Приоритет источников: runtime evidence, затем code/tests/configuration на
проверенном HEAD, затем tracked repository documentation, затем общая карта.
Карта не является build/runtime dependency и не заменяет фактический код либо
runtime evidence. Недоступную карту отмечать `PROJECT_MAP_UNAVAILABLE`;
несовпадение её commit/runtime status с фактическими данными —
`PROJECT_MAP_STALE`.
Инварианты P_Rec_Annot: остаётся единственная NETLOAD entry assembly, aliases
и статусы команд не исчезают молча, Smoke/DryRun не пишут DWG, а Apply требует
явного разрешения, локальной fixture, ограниченной transaction, evidence и
проверяемого rollback/UNDO. Не ослаблять подтверждённый Ortho source/DataLinks
контракт и DCF control comparison ради сведений из карты.
После изменения plugin architecture или runtime-status в отчёте перечислять
документы карты, требующие обновления: запись P_Rec_Annot, command/status,
Ortho/DataLinks contract, DryRun/Apply/UNDO evidence и migration status. Не
утверждать обновление внешней документации без фактического изменения.
@@ -0,0 +1,3 @@
using Autodesk.AutoCAD.Runtime;
[assembly: CommandClass(typeof(PRecAnnot.Plant3D2026.EntryPoint))]
@@ -47,8 +47,20 @@ public sealed class EntryPoint : IExtensionApplication
[CommandMethod("PHP_REC_AUTOMATION_PREPARE", CommandFlags.Session)]
public void AutomationPrepareCommand() => ScriptDrivenFirstApplyAutomation.Prepare();
[CommandMethod("PHP_REC_AUTOMATION_BEFORE_PXP_1", CommandFlags.Session)]
public void AutomationBeforePxp1Command() => ScriptDrivenFirstApplyAutomation.RecordSentinel("BeforePxp1");
[CommandMethod("PHP_REC_AUTOMATION_AFTER_PXP_1", CommandFlags.Session)]
public void AutomationAfterPxp1Command() => ScriptDrivenFirstApplyAutomation.RecordSentinel("AfterPxp1");
[CommandMethod("PHP_REC_AUTOMATION_BEFORE_PXP_2", CommandFlags.Session)]
public void AutomationBeforePxp2Command() => ScriptDrivenFirstApplyAutomation.RecordSentinel("BeforePxp2");
[CommandMethod("PHP_REC_AUTOMATION_AFTER_PXP_2", CommandFlags.Session)]
public void AutomationAfterPxp2Command() => ScriptDrivenFirstApplyAutomation.RecordSentinel("AfterPxp2");
[CommandMethod("PHP_REC_AUTOMATION_FINALIZE", CommandFlags.Session)]
public void AutomationFinalizeCommand() => ScriptDrivenFirstApplyAutomation.FinalizePreview();
public void AutomationFinalizeCommand() => ScriptDrivenFirstApplyAutomation.FinalizeAutomation();
private static void RunDiagnostic(string commandName)
{
@@ -61,7 +73,13 @@ public sealed class EntryPoint : IExtensionApplication
document = AcadApplication.DocumentManager.MdiActiveDocument;
var mode = ParseMode(RuntimeDiagnostics.RuntimeMode);
var context = new PlantRuntimeContextReader().ReadCurrentContext();
// Script-driven commands can run while Plant's implicit current
// document lags behind MDI activation. Bind the planner to the
// command document captured above, which is also the observer's
// fixture identity.
var context = ScriptDrivenFirstApplyAutomation.TryGetPreparedContext(document, out var scriptContext)
? scriptContext
: new PlantRuntimeContextReader().ReadCurrentContext(document);
var details = new Dictionary<string, object?>
{
["ExecutionMode"] = mode.ToString(),
@@ -97,7 +115,8 @@ public sealed class EntryPoint : IExtensionApplication
var applyPermit = (mode == PRecAnnotExecutionMode.Apply || hasLimitedApplyPermit) && document is not null
? FirstLimitedApplyPermitReader.ReadFor(document)
: null;
var automationTarget = FirstLimitedApplyAutomationCoordinator.SelectedTargetSnapshotId;
var automationTarget = ScriptDrivenFirstApplyAutomation.SelectedTargetSnapshotId
?? FirstLimitedApplyAutomationCoordinator.SelectedTargetSnapshotId;
var limitedTarget = applyPermit?.TargetSnapshotId ?? automationTarget;
if (mode == PRecAnnotExecutionMode.Apply && context.Viewports.Count != 1)
throw new InvalidOperationException("APPLY_REQUIRES_EXACTLY_ONE_VIEWPORT");
@@ -181,7 +200,8 @@ public sealed class EntryPoint : IExtensionApplication
new RuntimeWriteGuard(mode).EnsureWriteAllowed(preflight.Operation.OperationId);
RuntimeDiagnostics.Write("ApplyPreflightCompleted", operationId: operationId, commandName: commandName, resultStatus: "Accepted", 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, applyPermit.AnnotationTemplateDwgPath);
ScriptDrivenFirstApplyAutomation.RecordApplyOutcome(outcome);
FirstLimitedApplyAutomationCoordinator.RecordApplyOutcome(outcome);
details["ApplyOutcome"] = outcome.ToString();
details["AnnotationsCreated"] = outcome == PipeAnnotationWriteOutcome.Created ? 1 : 0;
@@ -198,6 +218,8 @@ public sealed class EntryPoint : IExtensionApplication
}
catch (System.Exception exception)
{
if (string.Equals(commandName, "PXP-OA", StringComparison.OrdinalIgnoreCase) && string.Equals(Environment.GetEnvironmentVariable("P_REC_ANNOT_RUNTIME_MODE"), "Apply", StringComparison.OrdinalIgnoreCase))
ScriptDrivenFirstApplyAutomation.RecordApplyFailure(exception);
RuntimeDiagnostics.Write("CommandFailed", "ERROR", operationId, commandName, exception.Message, exception, document);
WriteUsage(operationId, commandName, exception.Message, document, null, stopwatch.ElapsedMilliseconds, exception);
WriteEditor(document, $"\nP_Rec_Annot: безопасная диагностика завершилась ошибкой: {exception.Message}");
@@ -21,6 +21,9 @@ internal sealed record FirstApplyAutomationConfiguration
}
internal sealed record AutomationEntityCounts(int Total, int PipeBlocks, int PxpOaXdata, int Owned);
internal sealed record PxpOaDiscoveryEvidence(bool Found, string? GlobalName, string? GroupName, string? Flags, string? DeclaringType, string? DeclaringAssembly, string? AssemblyLocation, string? AssemblySha256, bool AssemblyLoaded, bool CommandClassIncluded, bool BundleHashMatched, int DeclarationCount);
internal sealed record AutomationSentinelEvidence(string Stage, string? DocumentIdentity, string? DatabaseIdentity, DateTimeOffset TimestampUtc);
internal sealed record AutomationCommandCorrelationEvidence(string EventKind, string RawCommandName, string NormalizedCommandName, bool CommandNameMatched, bool PreparedStateMatched, bool DocumentMatched, bool DatabaseMatched, bool FixturePathMatched, bool SequenceAccepted, string? RejectionReason, string? SenderType, string? SenderDocumentPath, string? ExpectedDocumentPath, string? ActiveDocumentPath, int RunCount, DateTimeOffset TimestampUtc);
internal static class FirstLimitedApplyAutomationBootstrap
{
@@ -122,6 +125,10 @@ internal sealed record FirstApplyAutomationEvidence
public string? PreviousState { get; init; }
public string? CurrentState { get; init; }
public IReadOnlyList<string> StateTransitions { get; init; } = Array.Empty<string>();
public PxpOaDiscoveryEvidence? PxpOaDiscovery { get; init; }
public IReadOnlyList<AutomationSentinelEvidence> Sentinels { get; init; } = Array.Empty<AutomationSentinelEvidence>();
public IReadOnlyList<string> CommandEvents { get; init; } = Array.Empty<string>();
public IReadOnlyList<AutomationCommandCorrelationEvidence> CommandCorrelations { get; init; } = Array.Empty<AutomationCommandCorrelationEvidence>();
}
internal sealed class FirstLimitedApplyAutomationCoordinator
@@ -16,6 +16,9 @@ internal sealed record FirstLimitedApplyRuntimePermit
public required string TargetSnapshotId { get; init; }
public required string ExpectedPlanId { get; init; }
public required string VisibilityState { get; init; }
// Test-only local copy of the legacy v1.0.4 Template.dwg. The package
// deliberately carries no DWG assets; the per-run permit binds this source.
public required string AnnotationTemplateDwgPath { get; init; }
}
internal static class FirstLimitedApplyPermitReader
@@ -41,6 +44,8 @@ internal static class FirstLimitedApplyPermitReader
var drawing = Path.GetFullPath(document.Name);
if (!drawing.StartsWith(root + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase))
throw new InvalidOperationException("APPLY_TEST_FIXTURE_OR_PERMIT_REQUIRED");
if (string.IsNullOrWhiteSpace(permit.AnnotationTemplateDwgPath) || !Path.IsPathFullyQualified(permit.AnnotationTemplateDwgPath) || permit.AnnotationTemplateDwgPath.StartsWith("\\\\", StringComparison.Ordinal) || !File.Exists(permit.AnnotationTemplateDwgPath))
throw new InvalidOperationException("ANNOTATION_TEMPLATE_ASSET_REQUIRED");
return permit;
}
}
@@ -52,12 +57,12 @@ internal sealed class FirstLimitedPipeAnnotationWriter
private const string BlockName = "PIPE";
private const string XdataApplication = "PXP-OA";
public PipeAnnotationWriteOutcome Write(Document document, CreatePipeAnnotationOperation operation, string visibilityState)
public PipeAnnotationWriteOutcome Write(Document document, CreatePipeAnnotationOperation operation, string visibilityState, string annotationTemplateDwgPath)
{
var database = document.Database;
using var transaction = database.TransactionManager.StartTransaction();
var table = (BlockTable)transaction.GetObject(database.BlockTableId, OpenMode.ForRead);
if (!table.Has(BlockName)) throw new InvalidOperationException("ANNOTATION_TEMPLATE_BLOCK_MISSING");
var blockId = EnsureLegacyPipeBlock(transaction, database, table, annotationTemplateDwgPath);
var paper = (BlockTableRecord)transaction.GetObject(table[BlockTableRecord.PaperSpace], OpenMode.ForWrite);
if (HasOwnedAnnotation(transaction, paper, operation.DuplicateKey)) return PipeAnnotationWriteOutcome.AlreadyExists;
@@ -65,7 +70,7 @@ internal sealed class FirstLimitedPipeAnnotationWriter
using var annotation = new BlockReference(
new Point3d(operation.Placement.X, operation.Placement.Y, operation.Placement.Z),
table[BlockName]);
blockId);
paper.AppendEntity(annotation);
transaction.AddNewlyCreatedDBObject(annotation, true);
SetConfirmedVisibility(annotation, visibilityState);
@@ -77,6 +82,27 @@ internal sealed class FirstLimitedPipeAnnotationWriter
return PipeAnnotationWriteOutcome.Created;
}
private static ObjectId EnsureLegacyPipeBlock(Transaction destinationTransaction, Database destination, BlockTable destinationTable, string templatePath)
{
if (destinationTable.Has(BlockName)) return destinationTable[BlockName];
if (!File.Exists(templatePath)) throw new InvalidOperationException("ANNOTATION_TEMPLATE_ASSET_MISSING");
using var source = new Database(false, true);
source.ReadDwgFile(templatePath, FileShare.Read, true, string.Empty);
source.CloseInput(true);
using var sourceTransaction = source.TransactionManager.StartTransaction();
var sourceTable = (BlockTable)sourceTransaction.GetObject(source.BlockTableId, OpenMode.ForRead);
if (!sourceTable.Has(BlockName)) throw new InvalidOperationException("ANNOTATION_TEMPLATE_PIPE_BLOCK_MISSING");
using var sourceIds = new ObjectIdCollection { sourceTable[BlockName] };
using var mapping = new IdMapping();
destination.WblockCloneObjects(sourceIds, destination.BlockTableId, mapping, DuplicateRecordCloning.Ignore, false);
sourceTransaction.Commit();
var refreshed = (BlockTable)destinationTransaction.GetObject(destination.BlockTableId, OpenMode.ForRead);
if (!refreshed.Has(BlockName)) throw new InvalidOperationException("ANNOTATION_TEMPLATE_BLOCK_IMPORT_FAILED");
return refreshed[BlockName];
}
private static bool HasOwnedAnnotation(Transaction transaction, BlockTableRecord paper, string duplicateKey)
{
foreach (ObjectId id in paper)
@@ -106,8 +132,9 @@ internal sealed class FirstLimitedPipeAnnotationWriter
var property = annotation.DynamicBlockReferencePropertyCollection
.Cast<DynamicBlockReferenceProperty>()
.SingleOrDefault(value => string.Equals(value.PropertyName, "V", StringComparison.Ordinal));
if (property is null || !property.GetAllowedValues().OfType<string>().Contains(visibilityState, StringComparer.Ordinal))
throw new InvalidOperationException("ANNOTATION_VISIBILITY_STATE_UNSUPPORTED");
var allowed = property?.GetAllowedValues().OfType<string>().ToArray() ?? [];
if (property is null || !allowed.Contains(visibilityState, StringComparer.Ordinal))
throw new InvalidOperationException($"ANNOTATION_VISIBILITY_STATE_UNSUPPORTED:Allowed={string.Join('|', allowed)}");
property.Value = visibilityState;
}
}
@@ -479,15 +479,22 @@ internal sealed class PlantViewportMembershipReader
private static bool TryReadPipePlacement(MdObjectId sourceId, DataLinksManager dataLinks, out GeometrySnapshot geometry)
{
geometry = new GeometrySnapshot();
var stage = "GetDatabase";
try
{
var openedByQuery = false;
var sourceDatabase = sourceId.GetDatabase(false, ref openedByQuery);
// The source model is normally not an active document in an Ortho /b session.
// Ask MdObjectId to open its source database read-only and dispose it below when it did.
var sourceDatabase = sourceId.GetDatabase(true, ref openedByQuery);
try
{
stage = "StartTransaction";
using var transaction = sourceDatabase.TransactionManager.StartOpenCloseTransaction();
stage = "MakeAcDbObjectId";
var objectId = dataLinks.MakeAcDbObjectId(sourceId.PrjObjectId, sourceDatabase);
stage = "OpenPipe";
if (transaction.GetObject(objectId, OpenMode.ForRead) is not Pipe pipe) return false;
stage = "TransformStartPoint";
var point = pipe.StartPoint.TransformBy(sourceId.Xform);
geometry = new GeometrySnapshot
{
@@ -501,8 +508,9 @@ internal sealed class PlantViewportMembershipReader
if (openedByQuery) sourceDatabase.Dispose();
}
}
catch
catch (Exception exception)
{
RuntimeDiagnostics.Write("OrthoPipePlacementReadFailed", "ERROR", resultStatus: "ORTHO_PIPE_PLACEMENT_UNAVAILABLE", exception: exception, details: new Dictionary<string, object?> { ["PlacementStage"] = stage });
return false;
}
}
@@ -1,9 +1,12 @@
using System.Text.Json;
using System.Reflection;
using System.Security.Cryptography;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.ProcessPower.PlantInstance;
using Autodesk.ProcessPower.ProjectManager;
using PRecAnnot.Abstractions;
using AcadApplication = Autodesk.AutoCAD.ApplicationServices.Application;
namespace PRecAnnot.Plant3D2026;
@@ -11,8 +14,8 @@ namespace PRecAnnot.Plant3D2026;
// Test-only observer. The AutoCAD /b processor, never this type, drives PXP-OA.
internal static class ScriptDrivenFirstApplyAutomation
{
private sealed record Config(string Phase, string OrthoDrawingPath, string EvidencePath, string FixtureMarkerPath);
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 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, 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 Document? fixture;
private static Database? database;
@@ -20,8 +23,18 @@ internal static class ScriptDrivenFirstApplyAutomation
private static int? dbmodBaseline;
private static readonly List<Run> runs = [];
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;
// Plant's implicit current-document lookup can lag between script commands.
// PREPARE has already proved this immutable context belongs to the live fixture;
// retain it for the two immediately following public PXP-OA commands only.
private static PlantRuntimeContext? preparedContext;
private static string? selectedTargetSnapshotId;
private static bool prepared;
private static bool terminal;
private static string? applyExceptionText;
internal static void Prepare()
{
@@ -30,6 +43,8 @@ internal static class ScriptDrivenFirstApplyAutomation
if (terminal) return;
config = ReadConfig();
RequireGate(config);
discovery = DiscoverPxpOa(config);
RequireDiscovery(discovery);
var active = AcadApplication.DocumentManager.MdiActiveDocument ?? throw new InvalidOperationException("AUTOMATION_ACTIVE_DOCUMENT_MISSING");
if (!SamePath(active.Name, config.OrthoDrawingPath)) throw new InvalidOperationException("AUTOMATION_UNEXPECTED_ACTIVE_FIXTURE");
fixture = active;
@@ -45,6 +60,12 @@ internal static class ScriptDrivenFirstApplyAutomation
var context = new PlantRuntimeContextReader().ReadCurrentContext(fixture);
if (!context.HasPlantProject || !context.IsOrthoDrawing || context.Viewports.Count != 1)
throw new InvalidOperationException("AUTOMATION_ORTHO_CONTEXT_INVALID");
var candidates = new PlantObjectReader().ReadObjects(context.Viewports.Single(), CancellationToken.None)
.Where(x => string.Equals(x.ObjectClass, "Pipe", StringComparison.OrdinalIgnoreCase) && x.MembershipStatus == ViewMembershipStatus.Resolved && x.Geometry.PaperPosition is not null)
.Select(x => x.SnapshotId).OrderBy(x => x, StringComparer.Ordinal).ToArray();
if (candidates.Length == 0) throw new InvalidOperationException("AUTOMATION_NO_ELIGIBLE_CANDIDATE");
preparedContext = context;
selectedTargetSnapshotId = candidates[0];
dbmodBaseline = Dbmod();
baseline = Counts(database);
prepared = true;
@@ -54,27 +75,75 @@ internal static class ScriptDrivenFirstApplyAutomation
catch (System.Exception exception) { Fail("AUTOMATION_PREPARE_FAILED", exception); }
}
internal static void RecordSentinel(string stage)
{
Guard(() =>
{
if (!prepared || terminal || fixture is null || database is null) return;
sentinels.Add(new(stage, fixture.Name, database.Filename, DateTimeOffset.UtcNow));
transitions.Add("Sentinel:" + stage);
Write(false, null);
});
}
internal static void RecordPxpOa(string? planId, IReadOnlyList<string> candidates, string? view, int create, int skip, bool? permitAccepted, int sourceRows, int membershipResolved)
{
if (!prepared || terminal || runs.Count == 0) return;
var last = runs[^1];
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 FinalizePreview()
internal static void RecordApplyOutcome(PipeAnnotationWriteOutcome outcome)
{
if (!prepared || terminal || runs.Count == 0) return;
var last = runs[^1];
runs[^1] = outcome == PipeAnnotationWriteOutcome.AlreadyExists
? last with { ApplyOutcome = outcome.ToString(), Create = 0, Skip = 1 }
: last with { ApplyOutcome = outcome.ToString() };
}
internal static void RecordApplyFailure(System.Exception exception)
{
if (!prepared || terminal) return;
applyExceptionText = exception.ToString();
Write(false, "AUTOMATION_APPLY_COMMAND_FAILED");
}
internal static bool TryGetPreparedContext(Document? commandDocument, out PlantRuntimeContext context)
{
context = null!;
if (!prepared || terminal || fixture is null || preparedContext is null || commandDocument is null || config is null) return false;
if (!SamePath(commandDocument.Name, config.OrthoDrawingPath) || !SamePath(fixture.Name, config.OrthoDrawingPath)) return false;
if (commandDocument.Database is null || database is null || !SamePath(commandDocument.Database.Filename, database.Filename)) return false;
context = preparedContext;
return true;
}
internal static string? SelectedTargetSnapshotId => prepared && !terminal ? selectedTargetSnapshotId : null;
internal static void FinalizeAutomation()
{
try
{
if (!prepared) throw new InvalidOperationException("AUTOMATION_NOT_PREPARED");
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 => 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 => x.Create != 1)) throw new InvalidOperationException("AUTOMATION_PREVIEW_CREATE_NOT_ONE");
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");
var apply = string.Equals(config!.Phase, "Apply", StringComparison.OrdinalIgnoreCase);
if ((!apply && runs.Any(x => x.Create != 1)) || (apply && (runs[0].Create != 1 || runs[1].Create != 0))) throw new InvalidOperationException("AUTOMATION_CREATE_COUNTS_INVALID");
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");
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;
transitions.Add("PreviewCompleted");
transitions.Add(apply ? "ApplyCompleted" : "PreviewCompleted");
Write(true, null);
Unsubscribe();
}
@@ -85,8 +154,11 @@ internal static class ScriptDrivenFirstApplyAutomation
{
Guard(() =>
{
if (!IsFixture(sender) || !IsPxp(args.GlobalCommandName) || runs.Count >= 2) return;
runs.Add(new Run(runs.Count + 1, null, [], null, 0, 0, Dbmod(), null, Counts(database!), null, true, false, false, false, null, null));
commandEvents.Add("WillStart:" + args.GlobalCommandName);
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, null, null, Dbmod(), null, Counts(database!), null, true, false, false, false, null, null));
transitions.Add("PxpOaWillStart:" + runs.Count);
});
}
@@ -97,7 +169,11 @@ internal static class ScriptDrivenFirstApplyAutomation
{
Guard(() =>
{
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!) };
@@ -105,11 +181,30 @@ 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 { } }
private static void Unsubscribe()
{
if (fixture is not null) try { fixture.CommandWillStart -= OnWillStart; fixture.CommandEnded -= OnEnded; fixture.CommandFailed -= OnFailed; fixture.CommandCancelled -= OnCancelled; } catch { }
preparedContext = null;
selectedTargetSnapshotId = null;
}
private static int? Dbmod() { try { return Convert.ToInt32(AcadApplication.GetSystemVariable("DBMOD")); } catch { return null; } }
private static AutomationEntityCounts Counts(Database db)
{
@@ -123,7 +218,7 @@ internal static class ScriptDrivenFirstApplyAutomation
{
var path = Environment.GetEnvironmentVariable("P_REC_ANNOT_AUTOMATION_CONFIG") ?? throw new InvalidOperationException("AUTOMATION_CONFIG_MISSING");
using var json = JsonDocument.Parse(File.ReadAllText(path)); var root = json.RootElement;
return new(root.GetProperty("phase").GetString() ?? "Preview", root.GetProperty("orthoDrawingPath").GetString() ?? throw new InvalidOperationException("AUTOMATION_ORTHO_PATH_REQUIRED"), root.GetProperty("evidencePath").GetString() ?? throw new InvalidOperationException("AUTOMATION_EVIDENCE_PATH_REQUIRED"), root.GetProperty("fixtureMarkerPath").GetString() ?? throw new InvalidOperationException("AUTOMATION_MARKER_PATH_REQUIRED"));
return new(root.GetProperty("phase").GetString() ?? "Preview", root.GetProperty("orthoDrawingPath").GetString() ?? throw new InvalidOperationException("AUTOMATION_ORTHO_PATH_REQUIRED"), root.GetProperty("evidencePath").GetString() ?? throw new InvalidOperationException("AUTOMATION_EVIDENCE_PATH_REQUIRED"), root.GetProperty("fixtureMarkerPath").GetString() ?? throw new InvalidOperationException("AUTOMATION_MARKER_PATH_REQUIRED"), root.GetProperty("entryAssemblySha256").GetString() ?? throw new InvalidOperationException("AUTOMATION_ENTRY_ASSEMBLY_HASH_REQUIRED"), root.TryGetProperty("diagnostic", out var diagnostic) && diagnostic.ValueKind == JsonValueKind.True);
}
private static void RequireGate(Config value)
{
@@ -134,8 +229,44 @@ 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 };
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, ExceptionText = applyExceptionText, 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);
}
private static PxpOaDiscoveryEvidence DiscoverPxpOa(Config value)
{
var entry = typeof(EntryPoint).Assembly;
var loaded = AppDomain.CurrentDomain.GetAssemblies().Any(x => ReferenceEquals(x, entry));
var location = entry.Location;
var hash = File.Exists(location) ? Convert.ToHexString(SHA256.HashData(File.ReadAllBytes(location))) : null;
var declarations = AppDomain.CurrentDomain.GetAssemblies().SelectMany(Declarations).Where(x => string.Equals(x.Name, "PXP-OA", StringComparison.OrdinalIgnoreCase)).ToArray();
var owner = declarations.SingleOrDefault(x => x.Assembly == entry);
var commandClass = entry.GetCustomAttributesData().Any(x => string.Equals(x.AttributeType.FullName, "Autodesk.AutoCAD.Runtime.CommandClassAttribute", StringComparison.Ordinal) && x.ConstructorArguments.Any(a => string.Equals(a.Value as Type, typeof(EntryPoint))));
return new(owner is not null, owner?.Name, owner?.Group, owner?.Flags, owner?.Type.FullName, owner?.Assembly.GetName().Name, location, hash, loaded, commandClass, hash is not null && string.Equals(hash, value.ExpectedEntryAssemblySha256, StringComparison.OrdinalIgnoreCase), declarations.Length);
}
private sealed record Declaration(string Name, string? Group, string? Flags, Type Type, Assembly Assembly);
private static IEnumerable<Declaration> Declarations(Assembly assembly)
{
Type[] types; try { types = assembly.GetTypes(); } catch (ReflectionTypeLoadException exception) { types = exception.Types.Where(x => x is not null).Cast<Type>().ToArray(); }
foreach (var type in types) foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)) foreach (var attribute in method.GetCustomAttributesData().Where(x => string.Equals(x.AttributeType.FullName, "Autodesk.AutoCAD.Runtime.CommandMethodAttribute", StringComparison.Ordinal)))
{
var name = attribute.ConstructorArguments.FirstOrDefault().Value as string;
if (string.IsNullOrWhiteSpace(name)) continue;
var group = attribute.NamedArguments.FirstOrDefault(x => x.MemberName == "GroupName").TypedValue.Value as string;
var flags = attribute.NamedArguments.FirstOrDefault(x => x.MemberName == "Flags").TypedValue.Value?.ToString();
yield return new(name, group, flags, type, assembly);
}
}
private static void RequireDiscovery(PxpOaDiscoveryEvidence value)
{
if (!value.AssemblyLoaded) throw new InvalidOperationException("PXP_OA_ASSEMBLY_NOT_LOADED");
if (!value.BundleHashMatched) throw new InvalidOperationException("PXP_OA_BUNDLE_HASH_MISMATCH");
if (!value.CommandClassIncluded) throw new InvalidOperationException("PXP_OA_COMMANDCLASS_NOT_REGISTERED");
if (value.DeclarationCount > 1) throw new InvalidOperationException("PXP_OA_COMMAND_COLLISION");
if (value.DeclarationCount == 0 || !value.Found) throw new InvalidOperationException("PXP_OA_COMMAND_NOT_FOUND");
if (!string.Equals(value.GlobalName, "PXP-OA", StringComparison.Ordinal)) throw new InvalidOperationException("PXP_OA_GLOBAL_NAME_MISMATCH");
}
}
@@ -0,0 +1,43 @@
using Xunit;
namespace PRecAnnot.Core.Tests;
public sealed class PxpOaRegistrationTests
{
private static string ProjectRoot
{
get
{
var directory = new DirectoryInfo(AppContext.BaseDirectory);
while (directory is not null && !File.Exists(Path.Combine(directory.FullName, "PRecAnnot2026.sln"))) directory = directory.Parent;
return Path.Combine(directory!.FullName, "src", "PRecAnnot.Plant3D2026");
}
}
private static string Entry => File.ReadAllText(Path.Combine(ProjectRoot, "EntryPoint.cs"));
private static string Observer => File.ReadAllText(Path.Combine(ProjectRoot, "ScriptDrivenFirstApplyAutomation.cs"));
[Fact] public void PxpOaDiscovery_FindsExactlyOneDeclaration() => Assert.Single(System.Text.RegularExpressions.Regex.Matches(Entry, "\\[CommandMethod\\(\"PXP-OA\"").Cast<object>());
[Fact] public void PxpOaDiscovery_RecordsGlobalName() => Assert.Contains("GlobalName", Observer);
[Fact] public void PxpOaDiscovery_RecordsCommandGroup() => Assert.Contains("GroupName", Observer);
[Fact] public void PxpOaDiscovery_RecordsFlags() => Assert.Contains("Flags", Observer);
[Fact] public void PxpOaDiscovery_RecordsDeclaringAssembly() => Assert.Contains("owner?.Assembly", Observer);
[Fact] public void PxpOaDiscovery_RequiresLoadedAssembly() => Assert.Contains("PXP_OA_ASSEMBLY_NOT_LOADED", Observer);
[Fact] public void PxpOaDiscovery_RequiresCommandClass() => Assert.Contains("PXP_OA_COMMANDCLASS_NOT_REGISTERED", Observer);
[Fact] public void PxpOaDiscovery_RejectsCollision() => Assert.Contains("PXP_OA_COMMAND_COLLISION", Observer);
[Fact] public void PxpOaDiscovery_RejectsBundleHashMismatch() => Assert.Contains("PXP_OA_BUNDLE_HASH_MISMATCH", Observer);
[Fact] public void Prepare_StopsOnPxpOaDiscoveryFailure() => Assert.True(Observer.IndexOf("RequireDiscovery(discovery)", StringComparison.Ordinal) < Observer.IndexOf("PlantProject.LoadProject", StringComparison.Ordinal));
[Fact] public void AutomationSentinel_DoesNotModifyDatabase() => Assert.DoesNotContain("StartTransaction", Entry[Entry.IndexOf("AutomationBeforePxp1Command", StringComparison.Ordinal)..Entry.IndexOf("private static void RunDiagnostic", StringComparison.Ordinal)]);
[Fact] public void AutomationSentinel_IsTestOnly() => Assert.Contains("RequireGate", Observer);
[Fact] public void Observer_RecordsRawEventBeforeCorrelation() => Assert.True(Observer.IndexOf("commandEvents.Add", Observer.IndexOf("OnWillStart", StringComparison.Ordinal), StringComparison.Ordinal) < Observer.IndexOf("Correlate(\"WillStart\"", StringComparison.Ordinal));
[Fact] public void Observer_RecordsEveryRejectionReason() => Assert.Contains("DOCUMENT_IDENTITY_MISMATCH", Observer);
[Fact] public void Observer_DoesNotUseManagedDocumentReferenceAsAcceptance() => Assert.DoesNotContain("ReferenceEquals(sender, fixture)", Observer);
[Fact] public void Observer_RecordsExactlyTwoSequentialRuns() => Assert.Contains("runs.Count < 2", Observer);
[Fact] public void Finalize_ReportsMissingPlanSeparatelyFromMissingRuns() => Assert.True(Observer.IndexOf("AUTOMATION_EXPECTS_EXACTLY_TWO_PXP_OA", StringComparison.Ordinal) < Observer.IndexOf("AUTOMATION_PLAN_IDS_DIFFER", StringComparison.Ordinal));
[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 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 PlacementReader_OpensSourceDatabaseWhenItIsNotActive() { var source = File.ReadAllText(Path.Combine(ProjectRoot, "PlantReadPath.cs")); Assert.Contains("sourceId.GetDatabase(true, ref openedByQuery)", source); }
}
+35
View File
@@ -0,0 +1,35 @@
# Capability ledger
This directory is the versioned, machine-readable register that prevents active
2019 capabilities from silently disappearing during the Autodesk 2026 migration.
It is product-neutral by schema: Plant 3D, Revit and Navisworks records use the
same required fields, while product adapters remain outside the ledger.
Current coverage is deliberately narrow: active, non-archive `P_Rec_Annot`
Plant 3D command registrations. The generated ledger does not claim to be a
complete inventory of the 2019 ecosystem. Launcher, profiles, palettes,
resources, Revit and Navisworks must be added as independently audited slices.
## Working journal — 2026-08-05
1. This work makes a real 2019 capability visible, owned and decidable rather
than treating the first migrated command as the programme boundary.
2. It closes the risk that active P_Rec_Annot commands disappear unnoticed.
3. The schema is reusable for Revit and Navisworks because it describes a
capability and its evidence, not a Plant API.
4. No Launcher or product domain logic is introduced.
5. The scope statement and source pointer make missing coverage explicit.
6. Equivalence is proven only by the record's verification method and runtime
evidence; `NOT REPORTED` remains explicit.
7. Users gain an accountable migration path for every currently-audited active
P_Rec_Annot command, including the already verified reference vertical.
## Regeneration and validation
```powershell
pwsh migration/tools/Build-CapabilityLedger.ps1
pwsh migration/tools/Test-CapabilityLedger.ps1
```
The builder consumes audited source records only, excludes `_archive`, writes
stable IDs, and retains unknowns instead of manufacturing a target decision.
@@ -0,0 +1,607 @@
{
"schemaVersion": 1,
"scope": "Plant 3D P_Rec_Annot active command slice generated from audited non-archive command registrations. This is a ledger foundation, not a claim of complete ecosystem inventory.",
"source": "migration/audits/p-rec-annot-v1.0.4/evidence/commands.csv",
"capabilities": [
{
"id": "PLANT3D-2019-CMD-BJ",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "PHPAnnotations\\PHPBlockJig.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: BJ",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-CAC",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "PHPAnnotations\\PHPAnnotations.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: CAC",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-CAC1",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "P3DNewMa\\NewMaControl.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: CAC1",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-CREATEANNOTATIONCOMMAND",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "PHPAnnotations\\PHPAnnotations.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: CreateAnnotationCommand",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-MTO-MA",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "P3DNewMa\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: mto-ma",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-MTO-MA1",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "P3DNewMa\\NewMaControl.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: mto-ma1",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-PXP-AAV",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "AutoAnnotateView\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-AAV",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "PRecAnnot.Plant3D2026",
"owner": "Plant annotation domain owner",
"migrationStatus": "RUNTIME_VERIFIED",
"verificationMethod": "Controlled Plant 3D 2026 Preview/Apply/repeated Apply/UNDO runtime acceptance",
"runtimeEvidence": "Accepted runtime session 7402fee6290b44438642f9204e8bdb6c; automation journal 0cf74ff1c90d4c69a10417904638c7b6",
"knownDifferences": "2026 implementation uses local immutable package staging and managed entry assembly; legacy loading mechanics are not retained.",
"decision": "MIGRATED_RUNTIME_VERIFIED"
},
{
"id": "PLANT3D-2019-CMD-PXP-AC",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "SwapAttributesMenuItem\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-AC",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-PXP-AE",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "AutoElevation\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-AE",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-PXP-ALC",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "AutoLinenumberConnections\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-ALC",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-PXP-COV",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "CheckOrthoView\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-COV",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-PXP-DJ",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "OrthoAutoAnnotate\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-DJ",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-PXP-ELN",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "AnnotateLineNumbers\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-ELN",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-PXP-ESN",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "AnnotateLineNumbers\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-ESN",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-PXP-GIT",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "ExtentsIntersect\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-GIT",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-PXP-IE",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "InsertElevation\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-IE",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-PXP-IOD",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "UpdateOrthoDrawings\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-IOD",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-PXP-LN",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "AnnotateLineNumbers\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-LN",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-PXP-MA",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "PHPAnnotations\\PHPAnnotations.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-MA",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-PXP-OA",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "OrthoAutoAnnotate\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-OA",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "PRecAnnot.Plant3D2026",
"owner": "Plant annotation domain owner",
"migrationStatus": "RUNTIME_VERIFIED",
"verificationMethod": "Controlled Plant 3D 2026 Preview/Apply/repeated Apply/UNDO runtime acceptance",
"runtimeEvidence": "Accepted runtime session 7402fee6290b44438642f9204e8bdb6c; automation journal 0cf74ff1c90d4c69a10417904638c7b6",
"knownDifferences": "2026 implementation uses local immutable package staging and managed entry assembly; legacy loading mechanics are not retained.",
"decision": "MIGRATED_RUNTIME_VERIFIED"
},
{
"id": "PLANT3D-2019-CMD-PXP-OSI",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "OrthoAutoAnnotate\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-OSI",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-PXP-RA",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "OrthoAutoAnnotate\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-RA",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-PXP-RMP",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "AnnotateLineNumbers\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-RMP",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-PXP-ULN",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "AnnotateLineNumbers\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-ULN",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-PXP-UOB",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "UpdateOrthoViewBlocks\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-UOB",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
}
]
}
+35
View File
@@ -0,0 +1,35 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Autodesk 2019 to 2026 capability ledger",
"type": "object",
"required": ["schemaVersion", "scope", "capabilities"],
"properties": {
"schemaVersion": { "const": 1 },
"scope": { "type": "string", "minLength": 1 },
"capabilities": {
"type": "array",
"items": {
"type": "object",
"required": ["id", "sourceProduct", "sourceComponent", "userPurpose", "launchMethod", "dependencies", "resources", "projectConstraints", "legacyStatus", "targetComponent", "owner", "migrationStatus", "verificationMethod", "runtimeEvidence", "knownDifferences", "decision"],
"properties": {
"id": { "type": "string", "pattern": "^[A-Z0-9-]+$" },
"sourceProduct": { "type": "string" },
"sourceComponent": { "type": "string" },
"userPurpose": { "type": "string" },
"launchMethod": { "type": "string" },
"dependencies": { "type": "array", "items": { "type": "string" } },
"resources": { "type": "array", "items": { "type": "string" } },
"projectConstraints": { "type": "string" },
"legacyStatus": { "type": "string" },
"targetComponent": { "type": "string" },
"owner": { "type": "string" },
"migrationStatus": { "type": "string" },
"verificationMethod": { "type": "string" },
"runtimeEvidence": { "type": "string" },
"knownDifferences": { "type": "string" },
"decision": { "type": "string" }
}
}
}
}
}
@@ -0,0 +1,62 @@
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[string]$RepositoryRoot,
[Parameter(Mandatory = $false)]
[string]$OutputPath
)
$ErrorActionPreference = 'Stop'
if (-not $RepositoryRoot) { $RepositoryRoot = Split-Path -Parent $PSScriptRoot }
$RepositoryRoot = [IO.Path]::GetFullPath($RepositoryRoot)
$commandsPath = Join-Path $RepositoryRoot 'audits\p-rec-annot-v1.0.4\evidence\commands.csv'
if (-not (Test-Path -LiteralPath $commandsPath -PathType Leaf)) { throw "CAPABILITY_LEDGER_COMMAND_SOURCE_MISSING: $commandsPath" }
if (-not $OutputPath) { $OutputPath = Join-Path $RepositoryRoot 'capability-ledger\plant3d-p-rec-annot-commands.json' }
$OutputPath = [IO.Path]::GetFullPath($OutputPath)
$accepted = @{
'PXP-AAV' = @{ Component = 'PRecAnnot.Plant3D2026'; Evidence = 'Accepted runtime session 7402fee6290b44438642f9204e8bdb6c; automation journal 0cf74ff1c90d4c69a10417904638c7b6'; Decision = 'MIGRATED_RUNTIME_VERIFIED' }
'PXP-OA' = @{ Component = 'PRecAnnot.Plant3D2026'; Evidence = 'Accepted runtime session 7402fee6290b44438642f9204e8bdb6c; automation journal 0cf74ff1c90d4c69a10417904638c7b6'; Decision = 'MIGRATED_RUNTIME_VERIFIED' }
}
$commands = Import-Csv -LiteralPath $commandsPath | Where-Object { $_.File -notmatch '(^|\\)_archive(\\|$)' } |
Group-Object Command | ForEach-Object { $_.Group | Select-Object -First 1 } | Sort-Object Command
$capabilities = foreach ($command in $commands) {
$acceptedRecord = $accepted[$command.Command]
$status = if ($acceptedRecord) { 'RUNTIME_VERIFIED' } else { 'MIGRATION_PLANNED' }
[ordered]@{
id = ('PLANT3D-2019-CMD-' + ($command.Command.ToUpperInvariant() -replace '[^A-Z0-9]+', '-').Trim('-'))
sourceProduct = 'Autodesk Plant 3D 2019'
sourceComponent = $command.File
userPurpose = 'Registered AutoCAD command; command body and workflow require source-specific acceptance.'
launchMethod = ('AutoCAD command: ' + $command.Command)
dependencies = @('Plant 3D drawing', 'P_Rec_Annot legacy module')
resources = @('Project configuration', 'Drawing and annotation resources')
projectConstraints = ('Plant project: ' + $command.PlantProject + '; reads drawing: ' + $command.ReadsDrawing + '; writes drawing: ' + $command.WritesDrawing)
legacyStatus = 'ACTIVE_LEGACY'
targetComponent = if ($acceptedRecord) { $acceptedRecord.Component } else { 'UNASSIGNED' }
owner = if ($acceptedRecord) { 'Plant annotation domain owner' } else { 'UNASSIGNED' }
migrationStatus = $status
verificationMethod = if ($acceptedRecord) { 'Controlled Plant 3D 2026 Preview/Apply/repeated Apply/UNDO runtime acceptance' } else { 'Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance' }
runtimeEvidence = if ($acceptedRecord) { $acceptedRecord.Evidence } else { 'NOT REPORTED' }
knownDifferences = if ($acceptedRecord) { '2026 implementation uses local immutable package staging and managed entry assembly; legacy loading mechanics are not retained.' } else { 'NOT ASSESSED' }
decision = if ($acceptedRecord) { $acceptedRecord.Decision } else { 'MIGRATION_REQUIRED' }
}
}
$ledger = [ordered]@{
schemaVersion = 1
scope = 'Plant 3D P_Rec_Annot active command slice generated from audited non-archive command registrations. This is a ledger foundation, not a claim of complete ecosystem inventory.'
source = 'migration/audits/p-rec-annot-v1.0.4/evidence/commands.csv'
capabilities = @($capabilities)
}
$parent = Split-Path -Parent $OutputPath
New-Item -ItemType Directory -Path $parent -Force | Out-Null
$temporary = Join-Path $parent ((Split-Path -Leaf $OutputPath) + '.tmp')
$json = $ledger | ConvertTo-Json -Depth 8
[IO.File]::WriteAllText($temporary, $json + [Environment]::NewLine, [Text.UTF8Encoding]::new($false))
Move-Item -LiteralPath $temporary -Destination $OutputPath -Force
Write-Output "CAPABILITY_LEDGER_WRITTEN=$OutputPath"
Write-Output "CAPABILITY_COUNT=$($capabilities.Count)"
+23
View File
@@ -0,0 +1,23 @@
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[string]$RepositoryRoot
)
$ErrorActionPreference = 'Stop'
if (-not $RepositoryRoot) { $RepositoryRoot = Split-Path -Parent $PSScriptRoot }
$RepositoryRoot = [IO.Path]::GetFullPath($RepositoryRoot)
$ledgerPath = Join-Path $RepositoryRoot 'capability-ledger\plant3d-p-rec-annot-commands.json'
$sourcePath = Join-Path $RepositoryRoot 'audits\p-rec-annot-v1.0.4\evidence\commands.csv'
& (Join-Path $PSScriptRoot 'Build-CapabilityLedger.ps1') -RepositoryRoot $RepositoryRoot -OutputPath $ledgerPath | Out-Null
$ledger = Get-Content -LiteralPath $ledgerPath -Raw -Encoding utf8 | ConvertFrom-Json
$required = 'id','sourceProduct','sourceComponent','userPurpose','launchMethod','dependencies','resources','projectConstraints','legacyStatus','targetComponent','owner','migrationStatus','verificationMethod','runtimeEvidence','knownDifferences','decision'
$expected = (Import-Csv -LiteralPath $sourcePath | Where-Object { $_.File -notmatch '(^|\\)_archive(\\|$)' } | Select-Object -ExpandProperty Command -Unique).Count
if ($ledger.schemaVersion -ne 1) { throw 'CAPABILITY_LEDGER_SCHEMA_VERSION_INVALID' }
if ($ledger.capabilities.Count -ne $expected) { throw "CAPABILITY_LEDGER_COUNT_MISMATCH: expected $expected, got $($ledger.capabilities.Count)" }
if (($ledger.capabilities.id | Select-Object -Unique).Count -ne $ledger.capabilities.Count) { throw 'CAPABILITY_LEDGER_DUPLICATE_ID' }
foreach ($capability in $ledger.capabilities) { foreach ($field in $required) { if (-not $capability.PSObject.Properties.Name.Contains($field)) { throw "CAPABILITY_LEDGER_REQUIRED_FIELD_MISSING: $($capability.id):$field" } } }
$accepted = @($ledger.capabilities | Where-Object { $_.decision -eq 'MIGRATED_RUNTIME_VERIFIED' })
if ($accepted.Count -ne 2 -or ($accepted.id -notcontains 'PLANT3D-2019-CMD-PXP-AAV') -or ($accepted.id -notcontains 'PLANT3D-2019-CMD-PXP-OA')) { throw 'CAPABILITY_LEDGER_REFERENCE_VERTICAL_MISSING' }
Write-Output "CAPABILITY_LEDGER_VALIDATED=$ledgerPath"
Write-Output "CAPABILITY_COUNT=$($ledger.capabilities.Count)"