Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
060f1c9ac1 | ||
|
|
c3ac183fff | ||
|
|
68f500692c | ||
|
|
86cabb584b | ||
|
|
aefd3aa487 | ||
|
|
07ab3c436f | ||
|
|
c97f02c550 | ||
|
|
62f470c7ba | ||
|
|
2fe1ca71df | ||
|
|
e707456ab6 | ||
|
|
3cecee4777 | ||
|
|
8a9f95a605 | ||
|
|
c270a182ce |
@@ -127,3 +127,37 @@ formatting в одном commit.
|
|||||||
runtime-сценарий; записаны фактические результаты; выполнен rollback; fixture и
|
runtime-сценарий; записаны фактические результаты; выполнен rollback; fixture и
|
||||||
production изолированы; механика документирована; ветка опубликована обычным
|
production изолированы; механика документирована; ветка опубликована обычным
|
||||||
push.
|
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 и запись затронутой подсистемы); не заявлять,
|
||||||
|
что они уже обновлены, без фактического изменения.
|
||||||
|
|||||||
@@ -87,3 +87,33 @@ Autodesk properties по строковым именам.
|
|||||||
одного закрытого состояния проекта. Сравнивать нужно `control delta` с
|
одного закрытого состояния проекта. Сравнивать нужно `control delta` с
|
||||||
`DryRun delta`: одинаковые изменения классифицируются как baseline behavior
|
`DryRun delta`: одинаковые изменения классифицируются как baseline behavior
|
||||||
Plant 3D, а plugin-only изменения блокируют дальнейшую работу.
|
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)]
|
[CommandMethod("PHP_REC_AUTOMATION_PREPARE", CommandFlags.Session)]
|
||||||
public void AutomationPrepareCommand() => ScriptDrivenFirstApplyAutomation.Prepare();
|
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)]
|
[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)
|
||||||
{
|
{
|
||||||
@@ -61,7 +73,13 @@ public sealed class EntryPoint : IExtensionApplication
|
|||||||
document = AcadApplication.DocumentManager.MdiActiveDocument;
|
document = AcadApplication.DocumentManager.MdiActiveDocument;
|
||||||
var mode = ParseMode(RuntimeDiagnostics.RuntimeMode);
|
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?>
|
var details = new Dictionary<string, object?>
|
||||||
{
|
{
|
||||||
["ExecutionMode"] = mode.ToString(),
|
["ExecutionMode"] = mode.ToString(),
|
||||||
@@ -97,7 +115,8 @@ public sealed class EntryPoint : IExtensionApplication
|
|||||||
var applyPermit = (mode == PRecAnnotExecutionMode.Apply || hasLimitedApplyPermit) && document is not null
|
var applyPermit = (mode == PRecAnnotExecutionMode.Apply || hasLimitedApplyPermit) && document is not null
|
||||||
? FirstLimitedApplyPermitReader.ReadFor(document)
|
? FirstLimitedApplyPermitReader.ReadFor(document)
|
||||||
: null;
|
: null;
|
||||||
var automationTarget = FirstLimitedApplyAutomationCoordinator.SelectedTargetSnapshotId;
|
var automationTarget = ScriptDrivenFirstApplyAutomation.SelectedTargetSnapshotId
|
||||||
|
?? FirstLimitedApplyAutomationCoordinator.SelectedTargetSnapshotId;
|
||||||
var limitedTarget = applyPermit?.TargetSnapshotId ?? automationTarget;
|
var limitedTarget = applyPermit?.TargetSnapshotId ?? automationTarget;
|
||||||
if (mode == PRecAnnotExecutionMode.Apply && context.Viewports.Count != 1)
|
if (mode == PRecAnnotExecutionMode.Apply && context.Viewports.Count != 1)
|
||||||
throw new InvalidOperationException("APPLY_REQUIRES_EXACTLY_ONE_VIEWPORT");
|
throw new InvalidOperationException("APPLY_REQUIRES_EXACTLY_ONE_VIEWPORT");
|
||||||
@@ -181,7 +200,8 @@ public sealed class EntryPoint : IExtensionApplication
|
|||||||
new RuntimeWriteGuard(mode).EnsureWriteAllowed(preflight.Operation.OperationId);
|
new RuntimeWriteGuard(mode).EnsureWriteAllowed(preflight.Operation.OperationId);
|
||||||
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, applyPermit.AnnotationTemplateDwgPath);
|
||||||
|
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;
|
||||||
@@ -198,6 +218,8 @@ public sealed class EntryPoint : IExtensionApplication
|
|||||||
}
|
}
|
||||||
catch (System.Exception exception)
|
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);
|
RuntimeDiagnostics.Write("CommandFailed", "ERROR", operationId, commandName, exception.Message, exception, document);
|
||||||
WriteUsage(operationId, commandName, exception.Message, document, null, stopwatch.ElapsedMilliseconds, exception);
|
WriteUsage(operationId, commandName, exception.Message, document, null, stopwatch.ElapsedMilliseconds, exception);
|
||||||
WriteEditor(document, $"\nP_Rec_Annot: безопасная диагностика завершилась ошибкой: {exception.Message}");
|
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 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
|
internal static class FirstLimitedApplyAutomationBootstrap
|
||||||
{
|
{
|
||||||
@@ -122,6 +125,10 @@ internal sealed record FirstApplyAutomationEvidence
|
|||||||
public string? PreviousState { get; init; }
|
public string? PreviousState { get; init; }
|
||||||
public string? CurrentState { get; init; }
|
public string? CurrentState { get; init; }
|
||||||
public IReadOnlyList<string> StateTransitions { get; init; } = Array.Empty<string>();
|
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
|
internal sealed class FirstLimitedApplyAutomationCoordinator
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ internal sealed record FirstLimitedApplyRuntimePermit
|
|||||||
public required string TargetSnapshotId { get; init; }
|
public required string TargetSnapshotId { get; init; }
|
||||||
public required string ExpectedPlanId { get; init; }
|
public required string ExpectedPlanId { get; init; }
|
||||||
public required string VisibilityState { 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
|
internal static class FirstLimitedApplyPermitReader
|
||||||
@@ -41,6 +44,8 @@ internal static class FirstLimitedApplyPermitReader
|
|||||||
var drawing = Path.GetFullPath(document.Name);
|
var drawing = Path.GetFullPath(document.Name);
|
||||||
if (!drawing.StartsWith(root + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase))
|
if (!drawing.StartsWith(root + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase))
|
||||||
throw new InvalidOperationException("APPLY_TEST_FIXTURE_OR_PERMIT_REQUIRED");
|
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;
|
return permit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,12 +57,12 @@ internal sealed class FirstLimitedPipeAnnotationWriter
|
|||||||
private const string BlockName = "PIPE";
|
private const string BlockName = "PIPE";
|
||||||
private const string XdataApplication = "PXP-OA";
|
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;
|
var database = document.Database;
|
||||||
using var transaction = database.TransactionManager.StartTransaction();
|
using var transaction = database.TransactionManager.StartTransaction();
|
||||||
var table = (BlockTable)transaction.GetObject(database.BlockTableId, OpenMode.ForRead);
|
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);
|
var paper = (BlockTableRecord)transaction.GetObject(table[BlockTableRecord.PaperSpace], OpenMode.ForWrite);
|
||||||
if (HasOwnedAnnotation(transaction, paper, operation.DuplicateKey)) return PipeAnnotationWriteOutcome.AlreadyExists;
|
if (HasOwnedAnnotation(transaction, paper, operation.DuplicateKey)) return PipeAnnotationWriteOutcome.AlreadyExists;
|
||||||
@@ -65,7 +70,7 @@ internal sealed class FirstLimitedPipeAnnotationWriter
|
|||||||
|
|
||||||
using var annotation = new BlockReference(
|
using var annotation = new BlockReference(
|
||||||
new Point3d(operation.Placement.X, operation.Placement.Y, operation.Placement.Z),
|
new Point3d(operation.Placement.X, operation.Placement.Y, operation.Placement.Z),
|
||||||
table[BlockName]);
|
blockId);
|
||||||
paper.AppendEntity(annotation);
|
paper.AppendEntity(annotation);
|
||||||
transaction.AddNewlyCreatedDBObject(annotation, true);
|
transaction.AddNewlyCreatedDBObject(annotation, true);
|
||||||
SetConfirmedVisibility(annotation, visibilityState);
|
SetConfirmedVisibility(annotation, visibilityState);
|
||||||
@@ -77,6 +82,27 @@ internal sealed class FirstLimitedPipeAnnotationWriter
|
|||||||
return PipeAnnotationWriteOutcome.Created;
|
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)
|
private static bool HasOwnedAnnotation(Transaction transaction, BlockTableRecord paper, string duplicateKey)
|
||||||
{
|
{
|
||||||
foreach (ObjectId id in paper)
|
foreach (ObjectId id in paper)
|
||||||
@@ -106,8 +132,9 @@ internal sealed class FirstLimitedPipeAnnotationWriter
|
|||||||
var property = annotation.DynamicBlockReferencePropertyCollection
|
var property = annotation.DynamicBlockReferencePropertyCollection
|
||||||
.Cast<DynamicBlockReferenceProperty>()
|
.Cast<DynamicBlockReferenceProperty>()
|
||||||
.SingleOrDefault(value => string.Equals(value.PropertyName, "V", StringComparison.Ordinal));
|
.SingleOrDefault(value => string.Equals(value.PropertyName, "V", StringComparison.Ordinal));
|
||||||
if (property is null || !property.GetAllowedValues().OfType<string>().Contains(visibilityState, StringComparer.Ordinal))
|
var allowed = property?.GetAllowedValues().OfType<string>().ToArray() ?? [];
|
||||||
throw new InvalidOperationException("ANNOTATION_VISIBILITY_STATE_UNSUPPORTED");
|
if (property is null || !allowed.Contains(visibilityState, StringComparer.Ordinal))
|
||||||
|
throw new InvalidOperationException($"ANNOTATION_VISIBILITY_STATE_UNSUPPORTED:Allowed={string.Join('|', allowed)}");
|
||||||
property.Value = visibilityState;
|
property.Value = visibilityState;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -479,15 +479,22 @@ internal sealed class PlantViewportMembershipReader
|
|||||||
private static bool TryReadPipePlacement(MdObjectId sourceId, DataLinksManager dataLinks, out GeometrySnapshot geometry)
|
private static bool TryReadPipePlacement(MdObjectId sourceId, DataLinksManager dataLinks, out GeometrySnapshot geometry)
|
||||||
{
|
{
|
||||||
geometry = new GeometrySnapshot();
|
geometry = new GeometrySnapshot();
|
||||||
|
var stage = "GetDatabase";
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var openedByQuery = false;
|
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
|
try
|
||||||
{
|
{
|
||||||
|
stage = "StartTransaction";
|
||||||
using var transaction = sourceDatabase.TransactionManager.StartOpenCloseTransaction();
|
using var transaction = sourceDatabase.TransactionManager.StartOpenCloseTransaction();
|
||||||
|
stage = "MakeAcDbObjectId";
|
||||||
var objectId = dataLinks.MakeAcDbObjectId(sourceId.PrjObjectId, sourceDatabase);
|
var objectId = dataLinks.MakeAcDbObjectId(sourceId.PrjObjectId, sourceDatabase);
|
||||||
|
stage = "OpenPipe";
|
||||||
if (transaction.GetObject(objectId, OpenMode.ForRead) is not Pipe pipe) return false;
|
if (transaction.GetObject(objectId, OpenMode.ForRead) is not Pipe pipe) return false;
|
||||||
|
stage = "TransformStartPoint";
|
||||||
var point = pipe.StartPoint.TransformBy(sourceId.Xform);
|
var point = pipe.StartPoint.TransformBy(sourceId.Xform);
|
||||||
geometry = new GeometrySnapshot
|
geometry = new GeometrySnapshot
|
||||||
{
|
{
|
||||||
@@ -501,8 +508,9 @@ internal sealed class PlantViewportMembershipReader
|
|||||||
if (openedByQuery) sourceDatabase.Dispose();
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+147
-16
@@ -1,9 +1,12 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Security.Cryptography;
|
||||||
using Autodesk.AutoCAD.ApplicationServices;
|
using Autodesk.AutoCAD.ApplicationServices;
|
||||||
using Autodesk.AutoCAD.DatabaseServices;
|
using Autodesk.AutoCAD.DatabaseServices;
|
||||||
using Autodesk.AutoCAD.Runtime;
|
using Autodesk.AutoCAD.Runtime;
|
||||||
using Autodesk.ProcessPower.PlantInstance;
|
using Autodesk.ProcessPower.PlantInstance;
|
||||||
using Autodesk.ProcessPower.ProjectManager;
|
using Autodesk.ProcessPower.ProjectManager;
|
||||||
|
using PRecAnnot.Abstractions;
|
||||||
using AcadApplication = Autodesk.AutoCAD.ApplicationServices.Application;
|
using AcadApplication = Autodesk.AutoCAD.ApplicationServices.Application;
|
||||||
|
|
||||||
namespace PRecAnnot.Plant3D2026;
|
namespace PRecAnnot.Plant3D2026;
|
||||||
@@ -11,8 +14,8 @@ namespace PRecAnnot.Plant3D2026;
|
|||||||
// Test-only observer. The AutoCAD /b processor, never this type, drives PXP-OA.
|
// Test-only observer. The AutoCAD /b processor, never this type, drives PXP-OA.
|
||||||
internal static class ScriptDrivenFirstApplyAutomation
|
internal static class ScriptDrivenFirstApplyAutomation
|
||||||
{
|
{
|
||||||
private sealed record Config(string Phase, string OrthoDrawingPath, string EvidencePath, string FixtureMarkerPath);
|
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;
|
||||||
@@ -20,8 +23,18 @@ internal static class ScriptDrivenFirstApplyAutomation
|
|||||||
private static int? dbmodBaseline;
|
private static int? dbmodBaseline;
|
||||||
private static readonly List<Run> runs = [];
|
private static readonly List<Run> runs = [];
|
||||||
private static readonly List<string> transitions = [];
|
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 prepared;
|
||||||
private static bool terminal;
|
private static bool terminal;
|
||||||
|
private static string? applyExceptionText;
|
||||||
|
|
||||||
internal static void Prepare()
|
internal static void Prepare()
|
||||||
{
|
{
|
||||||
@@ -30,6 +43,8 @@ internal static class ScriptDrivenFirstApplyAutomation
|
|||||||
if (terminal) return;
|
if (terminal) return;
|
||||||
config = ReadConfig();
|
config = ReadConfig();
|
||||||
RequireGate(config);
|
RequireGate(config);
|
||||||
|
discovery = DiscoverPxpOa(config);
|
||||||
|
RequireDiscovery(discovery);
|
||||||
var active = AcadApplication.DocumentManager.MdiActiveDocument ?? throw new InvalidOperationException("AUTOMATION_ACTIVE_DOCUMENT_MISSING");
|
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");
|
if (!SamePath(active.Name, config.OrthoDrawingPath)) throw new InvalidOperationException("AUTOMATION_UNEXPECTED_ACTIVE_FIXTURE");
|
||||||
fixture = active;
|
fixture = active;
|
||||||
@@ -45,6 +60,12 @@ internal static class ScriptDrivenFirstApplyAutomation
|
|||||||
var context = new PlantRuntimeContextReader().ReadCurrentContext(fixture);
|
var context = new PlantRuntimeContextReader().ReadCurrentContext(fixture);
|
||||||
if (!context.HasPlantProject || !context.IsOrthoDrawing || context.Viewports.Count != 1)
|
if (!context.HasPlantProject || !context.IsOrthoDrawing || context.Viewports.Count != 1)
|
||||||
throw new InvalidOperationException("AUTOMATION_ORTHO_CONTEXT_INVALID");
|
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();
|
dbmodBaseline = Dbmod();
|
||||||
baseline = Counts(database);
|
baseline = Counts(database);
|
||||||
prepared = true;
|
prepared = true;
|
||||||
@@ -54,27 +75,75 @@ internal static class ScriptDrivenFirstApplyAutomation
|
|||||||
catch (System.Exception exception) { Fail("AUTOMATION_PREPARE_FAILED", exception); }
|
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)
|
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;
|
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 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
|
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 (runs.Any(x => x.Create != 1)) throw new InvalidOperationException("AUTOMATION_PREVIEW_CREATE_NOT_ONE");
|
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 (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;
|
terminal = true;
|
||||||
transitions.Add("PreviewCompleted");
|
transitions.Add(apply ? "ApplyCompleted" : "PreviewCompleted");
|
||||||
Write(true, null);
|
Write(true, null);
|
||||||
Unsubscribe();
|
Unsubscribe();
|
||||||
}
|
}
|
||||||
@@ -85,8 +154,11 @@ internal static class ScriptDrivenFirstApplyAutomation
|
|||||||
{
|
{
|
||||||
Guard(() =>
|
Guard(() =>
|
||||||
{
|
{
|
||||||
if (!IsFixture(sender) || !IsPxp(args.GlobalCommandName) || runs.Count >= 2) return;
|
commandEvents.Add("WillStart:" + args.GlobalCommandName);
|
||||||
runs.Add(new Run(runs.Count + 1, null, [], null, 0, 0, Dbmod(), null, Counts(database!), null, true, false, false, false, null, null));
|
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);
|
transitions.Add("PxpOaWillStart:" + runs.Count);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -97,7 +169,11 @@ internal static class ScriptDrivenFirstApplyAutomation
|
|||||||
{
|
{
|
||||||
Guard(() =>
|
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];
|
var last = runs[^1];
|
||||||
if (last.Ended || last.Failed || last.Cancelled) return;
|
if (last.Ended || last.Failed || last.Cancelled) return;
|
||||||
runs[^1] = last with { Ended = !failed && !cancelled, Failed = failed, Cancelled = cancelled, DbmodAfter = Dbmod(), After = Counts(database!) };
|
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);
|
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 AutomationCommandCorrelationEvidence Correlate(string kind, object sender, string rawName, bool sequenceAccepted)
|
||||||
private static bool IsPxp(string name) => string.Equals(name.TrimStart('.', '_'), "PXP-OA", StringComparison.OrdinalIgnoreCase);
|
{
|
||||||
|
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 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 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 int? Dbmod() { try { return Convert.ToInt32(AcadApplication.GetSystemVariable("DBMOD")); } catch { return null; } }
|
||||||
private static AutomationEntityCounts Counts(Database db)
|
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");
|
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;
|
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)
|
private static void RequireGate(Config value)
|
||||||
{
|
{
|
||||||
@@ -134,8 +229,44 @@ 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 };
|
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)!);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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); }
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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)"
|
||||||
@@ -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)"
|
||||||
Reference in New Issue
Block a user