Compare commits

...
Author SHA1 Message Date
codex 060f1c9ac1 migration: add capability ledger foundation 2026-08-05 08:57:24 +03:00
codex c3ac183fff docs: link shared project map from agent instructions 2026-08-05 07:14:45 +03:00
codex 68f500692c fix: verify apply duplicate and capture runtime failures 2026-08-04 16:35:42 +03:00
codex 86cabb584b fix: import legacy Pipe annotation template 2026-08-04 14:59:51 +03:00
codex aefd3aa487 fix: finalize script-driven limited Apply evidence 2026-08-04 14:49:54 +03:00
codex 07ab3c436f fix: open source database for Ortho placement 2026-08-04 14:43:59 +03:00
codex c97f02c550 fix: identify failed Ortho pipe placement boundary 2026-08-04 14:34:15 +03:00
codex 62f470c7ba fix: retain Ortho placement failure stack trace 2026-08-04 14:32:30 +03:00
codex 2fe1ca71df fix: diagnose unavailable Ortho pipe placement 2026-08-04 14:30:29 +03:00
codex e707456ab6 fix: retain prepared Ortho context for script PXP-OA 2026-08-04 14:25:44 +03:00
codex 3cecee4777 fix: bind script PXP-OA planning to command document 2026-08-04 14:19:20 +03:00
codex 8a9f95a605 fix: correlate script-driven PXP-OA command events 2026-08-04 14:09:14 +03:00
codex c270a182ce fix: verify PXP-OA registration for script Preview 2026-08-04 12:23:36 +03:00
codex 25311e89f0 Merge remote-tracking branch 'gitea/design/autodesk-launcher-next-20260731' into migration/plant3d-2026 2026-08-04 11:24:32 +03:00
codex f5a70df68a Merge remote-tracking branch 'gitea/audit/launcher-source-architecture-20260731' into migration/plant3d-2026 2026-08-04 11:24:32 +03:00
codex 5725bc8737 Merge remote-tracking branch 'gitea/audit/p-rec-annot-legacy-loading-20260731' into migration/plant3d-2026 2026-08-04 11:24:32 +03:00
codex bfd31a3747 fix: activate fixture project in script preparation 2026-08-04 10:10:49 +03:00
codex c5cf142159 refactor: observe commands driven by AutoCAD automation script 2026-08-04 10:06:57 +03:00
codex bc481d4378 fix: execute automation commands in AutoCAD command context 2026-08-04 09:37:24 +03:00
codex 1fa25809de fix: queue readiness command for active fixture document 2026-08-04 09:26:43 +03:00
codex 6ed1e4086b fix: activate fixture command queue before dispatch 2026-08-04 07:39:21 +03:00
codex c633107273 fix: terminate automation commands through AutoCAD queue 2026-08-04 07:37:56 +03:00
codex 2d9fbdd474 fix: queue readiness barrier after fixture activation 2026-08-04 07:29:37 +03:00
codex 98564b807f fix: wait for AutoCAD command-queue readiness barrier 2026-08-04 07:28:32 +03:00
codex 1b4ff816ba fix: match active fixture document by stable name 2026-08-04 07:26:45 +03:00
codex 13578b7c9b fix: recover fixture activation raised during document open 2026-08-04 07:25:53 +03:00
codex 523d063ceb fix: preserve completed automation evidence when quit is unavailable 2026-08-04 07:22:27 +03:00
codex 31171c9ed8 test: correct lifecycle exit assertions 2026-08-03 17:24:13 +03:00
codex edf1640c4f fix: dispatch lifecycle exit through live document 2026-08-03 17:23:44 +03:00
codex 81a075e684 fix: exit lifecycle probe without document command queue 2026-08-03 17:21:08 +03:00
codex 32607e2f34 fix: defer Plant activation until document is quiescent 2026-08-03 17:19:01 +03:00
codex c9c4113e86 fix: queue native quit after automation commands 2026-08-03 17:14:14 +03:00
codex 33f91c26aa fix: close all automation-opened documents 2026-08-03 17:10:02 +03:00
codex ef615a1f8e fix: dispatch automation exit from final command 2026-08-03 17:07:40 +03:00
codex e7765cbdda fix: close live document before automation exit 2026-08-03 17:04:56 +03:00
codex d8e61b7a5e fix: exit lifecycle probe from verified idle 2026-08-03 17:02:18 +03:00
codex 79067b72de fix: contain automation callback failures 2026-08-03 16:58:18 +03:00
codex 44de0cf787 fix: dispatch controlled AutoCAD exit from idle 2026-08-03 16:49:57 +03:00
codex 239796aafe fix: arm automation on post-netload command 2026-08-03 16:43:32 +03:00
codex 8a1735408a fix: trace gated automation bootstrap 2026-08-03 16:41:15 +03:00
codex 544b329077 fix: defer Plant automation to first post-startup idle 2026-08-03 16:34:17 +03:00
codex 1563e2fae0 feat: expose limited Apply preview candidates 2026-08-03 11:00:13 +03:00
codex bd5f47d811 feat: implement first limited Pipe annotation Apply 2026-08-03 10:50:51 +03:00
codex 1bfb91bc4b feat: add first limited Pipe Apply preflight 2026-08-03 10:37:37 +03:00
codex 209b8b89b8 docs: record control versus DryRun project deltas 2026-08-03 10:25:49 +03:00
codex 31787fc05a docs: strengthen P_Rec_Annot mapping and Apply rules 2026-08-03 10:14:29 +03:00
codex 11cf97c476 docs: document legacy and Plant 3D 2026 viewport mapping APIs 2026-08-03 10:14:21 +03:00
codex 76d362d2c3 feat: resolve Ortho viewport membership from confirmed evidence 2026-08-03 10:14:11 +03:00
codex 6e4cc74e82 docs: define Launcher Next pilot and performance plan 2026-07-31 14:34:05 +03:00
codex 1127a8376f docs: define Autodesk launcher next architecture 2026-07-31 14:30:19 +03:00
codex 36178374db audit: document launcher source architecture 2026-07-31 14:06:39 +03:00
codex b33351117b audit: clarify legacy bootstrap evidence 2026-07-31 13:39:55 +03:00
codex 56dcce42f6 audit: document legacy P_Rec_Annot loading chain 2026-07-31 13:39:29 +03:00
79 changed files with 3538 additions and 29 deletions
+34
View File
@@ -127,3 +127,37 @@ formatting в одном commit.
runtime-сценарий; записаны фактические результаты; выполнен rollback; fixture и
production изолированы; механика документирована; ветка опубликована обычным
push.
## Общая карта проекта
Каноническая общая карта проекта находится в Google Drive:
`https://drive.google.com/drive/folders/1avnR0V8q7y5pfWRYwB6LPBEEyfpN7PpV`.
Локальная синхронизированная копия, если она доступна, определяется только
переменной среды `PHIM_PLANT3D_PROJECT_MAP_ROOT`; её путь не хардкодить.
Перед работой над архитектурой, миграцией или runtime-сценарием обязательно
прочитать доступные индекс/обзор карты, целевую архитектуру и migration status,
индекс runtime evidence, а также записи карты для затрагиваемых Launcher и
P_Rec_Annot. Затем сверить их с локальными AGENTS.md, релевантными tracked
документами, текущим кодом и тестами.
Источники истины имеют следующий приоритет: фактическое runtime evidence;
текущий код, tests и tracked configuration на проверенном Git HEAD; tracked
документация репозитория; общая карта проекта. Внешняя карта описывает и
связывает работу, но не является build/runtime dependency и не переопределяет
текущий код или runtime evidence.
Проверять свежесть каждой используемой записи карты по фактическим `git
rev-parse HEAD`, ветке и привязанному runtime evidence. Если карта или её
локальная копия недоступна, фиксировать `PROJECT_MAP_UNAVAILABLE`; если
зафиксированные commit/runtime status не совпадают с фактическими —
`PROJECT_MAP_STALE`. Эти статусы не разрешают выдумывать evidence: продолжать
по доступным локальным источникам и явно сообщать расхождение.
Системные инварианты сохраняются: Launcher доставляет versioned local package
с одним managed entry assembly и без Autodesk DLL; P_Rec_Annot подтверждает
реальное Plant/Ortho/DataLinks поведение runtime evidence, а не только build
или tests. После изменения архитектуры либо runtime-статуса в отчёте указывать
конкретные документы общей карты, которые требуют обновления (архитектура,
статус миграции, evidence index и запись затронутой подсистемы); не заявлять,
что они уже обновлены, без фактического изменения.
+40
View File
@@ -57,6 +57,16 @@ Bundle содержит только фактический runtime dependency g
Fixture: local Plant 3D 2026 project вне Git с настоящей Pipe, synthetic line
number, Ortho drawing/viewport и SHA baseline до DryRun.
## Viewport membership and source DataLinks
Legacy P_Rec_Annot source for read-only analysis is
`\\phpsrv03\NBIM\code\CS\P_Rec_Annot\v1.0.4\repo`.
A source object is not a member of an Ortho viewport without confirmed
evidence. A geometry-only filter is at most `PartiallyResolved`. Apply is
blocked by any `PLUGIN_ONLY_CHANGE` in a DWG, DCF, or project metadata found by
a paired CONTROL/DryRun comparison.
## Ortho source DataLinks and DCF control
DataLinks активного Ortho drawing не являются источником Piping rows. Piping
@@ -77,3 +87,33 @@ Autodesk properties по строковым именам.
одного закрытого состояния проекта. Сравнивать нужно `control delta` с
`DryRun delta`: одинаковые изменения классифицируются как baseline behavior
Plant 3D, а plugin-only изменения блокируют дальнейшую работу.
## Общая карта проекта
Каноническая общая карта проекта:
`https://drive.google.com/drive/folders/1avnR0V8q7y5pfWRYwB6LPBEEyfpN7PpV`.
Необязательная локальная копия доступна только через
`PHIM_PLANT3D_PROJECT_MAP_ROOT`; локальный путь синхронизации не хардкодить.
До изменения P_Rec_Annot читать доступные индекс карты, запись архитектуры и
статуса P_Rec_Annot, DryRun/Apply contract, карту Ortho/DataLinks и связанный
runtime evidence index. Сверять их с фактическим Git HEAD, текущими plugin
code/tests/configuration и runtime evidence конкретной fixture.
Приоритет источников: runtime evidence, затем code/tests/configuration на
проверенном HEAD, затем tracked repository documentation, затем общая карта.
Карта не является build/runtime dependency и не заменяет фактический код либо
runtime evidence. Недоступную карту отмечать `PROJECT_MAP_UNAVAILABLE`;
несовпадение её commit/runtime status с фактическими данными —
`PROJECT_MAP_STALE`.
Инварианты P_Rec_Annot: остаётся единственная NETLOAD entry assembly, aliases
и статусы команд не исчезают молча, Smoke/DryRun не пишут DWG, а Apply требует
явного разрешения, локальной fixture, ограниченной transaction, evidence и
проверяемого rollback/UNDO. Не ослаблять подтверждённый Ortho source/DataLinks
контракт и DCF control comparison ради сведений из карты.
После изменения plugin architecture или runtime-status в отчёте перечислять
документы карты, требующие обновления: запись P_Rec_Annot, command/status,
Ortho/DataLinks contract, DryRun/Apply/UNDO evidence и migration status. Не
утверждать обновление внешней документации без фактического изменения.
@@ -1,3 +1,26 @@
# DCF control comparison
# CONTROL versus DryRun DCF comparison
`DBMOD` covers only the active DWG. DCF and project metadata require paired control and DryRun fixture copies created from one fully closed state. This comparison has not yet been executed, so plugin-only DCF and metadata deltas remain unverified. Apply remains blocked.
DBMOD covers only the active drawing. This procedure separately compares
content-bearing project files after identical CONTROL and DryRun sessions.
1. With Plant 3D closed and the Launcher session `RolledBack`, copy the local
fixture to `<FROZEN_FIXTURE>`.
2. Create `<CONTROL_FIXTURE>` and `<DRYRUN_FIXTURE>` from that same frozen
state; calculate complete local SHA-256 inventories before each run.
3. Open model and Ortho drawings in CONTROL through the same Launcher DryRun
launch contract, but invoke no P_Rec_Annot command. Close without saving,
wait for Launcher rollback, then calculate the after inventory.
4. Repeat with DRYRUN and invoke each supported command twice. Close without
saving, wait for rollback, then calculate the after inventory.
5. Compare deltas by logical relative path. Classify each content-bearing file
as `UNCHANGED`, `AUTODESK_BASELINE_CHANGE`, `PLUGIN_ONLY_CHANGE`, or
`INCONCLUSIVE`.
Content-bearing groups include model DWG, Ortho DWG, `Piping.dcf`, remaining
DCF/database files, `Project.xml` or equivalent project metadata, and Ortho
metadata. Lock, cache, session, log, preview, and temporary files are reported
separately and cannot establish project invariance.
Any `PLUGIN_ONLY_CHANGE` blocks Apply. Full paths and hashes remain local;
repository evidence contains only logical fixture markers, groups, counts, and
classifications.
@@ -0,0 +1,66 @@
{
"schemaVersion": 1,
"executionMode": "DryRun",
"fixture": {
"frozen": "<FROZEN_FIXTURE>",
"control": "<CONTROL_FIXTURE>",
"dryRun": "<DRYRUN_FIXTURE>",
"trackedFileCount": 210
},
"membership": {
"mechanism": "IDwgViewDef.DwgQuery.Execute -> MdObjectId.PrjObjectId -> DataLinksManager.FindAcPpRowId",
"orthoViews": 1,
"sourceObjects": 7,
"resolved": 5,
"partiallyResolved": 0,
"outsideView": 2,
"notResolved": 0,
"evidenceCode": "VIEW_MEMBERSHIP_FROM_CONFIRMED_PNP_ID"
},
"commands": {
"PHP_REC_ANNOT": {
"planSchemaVersion": 1,
"run1PlanId": "plan-AD701C7AA38D99E4C28D7D6D",
"run2PlanId": "plan-AD701C7AA38D99E4C28D7D6D",
"planDeterministic": true,
"summaryDeterministic": true
},
"PXP-AAV": {
"planSchemaVersion": 1,
"run1PlanId": "plan-C4FCD62F4EEBA1D1948FA22B",
"run2PlanId": "plan-C4FCD62F4EEBA1D1948FA22B",
"planDeterministic": true,
"summaryDeterministic": true
},
"PXP-OA": {
"planSchemaVersion": 1,
"run1PlanId": "plan-C30D133E2D5B4BF4C0AF2ED1",
"run2PlanId": "plan-C30D133E2D5B4BF4C0AF2ED1",
"planDeterministic": true,
"summaryDeterministic": true
}
},
"projectFileComparison": {
"controlDeltaCount": 0,
"dryRunDeltaCount": 0,
"modelDwgPluginOnlyDelta": "UNCHANGED",
"orthoDwgPluginOnlyDelta": "UNCHANGED",
"pipingDcfPluginOnlyDelta": "UNCHANGED",
"projectMetadataPluginOnlyDelta": "UNCHANGED"
},
"dbmod": {
"pairedRun": null,
"reason": "Command-window output was not retained."
},
"launcher": {
"controlSession": "RolledBack",
"dryRunSession": "RolledBack",
"netloadCount": 1,
"profileArgumentPresent": false
},
"apply": {
"performed": false,
"authorised": false,
"reason": "All operations remain Skip; annotation rules are not yet implemented."
}
}
@@ -3,9 +3,11 @@
Date: 2026-08-03
The local test fixture's `TestPipeOrtho.dwg` was detected as Ortho with one
viewport. In `DryRun`, `PHP_REC_ANNOT` ran twice, `PXP-AAV` ran twice, and
`PXP-OA` ran once. All completed without an unhandled exception; `DBMOD` was
`0` before and after.
viewport. In the paired `DryRun`, `PHP_REC_ANNOT`, `PXP-AAV`, and `PXP-OA`
each ran twice. Plugin telemetry recorded all six preflight completions without
an unhandled exception. The command-window DBMOD output was not retained for
this paired run, so its DBMOD value is recorded as `NOT RECORDED` rather than
inferred from telemetry.
`PlantProject.Name` was available for this fixture and rendered as
`PRecAnnotTestProject`.
@@ -17,12 +19,28 @@ reference. The adapter resolves that reference through
`PipingProject.GetPnPDrawingFiles()` and reads
`DataLinksManager.SelectAcPpRowIds(PnPProjectDrawing.Id)`.
On the latest real DryRun, one source drawing was resolved and seven source
Plant objects were read. `PHP_REC_ANNOT` reported seven skipped operations,
zero writes, zero conflicts, and zero errors. This verifies source-model
DataLinks access; it does not yet verify exact source-object-to-viewport
membership or repeated-plan determinism.
On the paired real DryRun, one source drawing was resolved and seven source
Plant objects were read. The confirmed view query resolved five source rows to
the active viewport and classified two as outside the view. Each command
reported seven skipped operations, zero writes, zero conflicts, and zero
errors. The repeated plans were deterministic:
The test fixture SHA baseline changed for `Piping.dcf` before the first DryRun
command. This run therefore does not prove DCF invariance. A paired control
fixture versus DryRun fixture comparison remains required.
| Command | Run 1 / run 2 PlanId |
| --- | --- |
| `PHP_REC_ANNOT` | `plan-AD701C7AA38D99E4C28D7D6D` |
| `PXP-AAV` | `plan-C4FCD62F4EEBA1D1948FA22B` |
| `PXP-OA` | `plan-C30D133E2D5B4BF4C0AF2ED1` |
This verifies source-model DataLinks access, exact view membership through the
stored view query, and repeated-plan determinism. The remaining functional
limitation is annotation-rule coverage: all seven operations remain safe
`Skip` operations, so no first Apply is authorised.
The earlier single-fixture SHA baseline changed before the first command and
was therefore inconclusive. A fresh paired comparison used 210-file CONTROL
and DRYRUN copies created from the same closed frozen fixture. Both inventories
had zero SHA-256 delta after their respective sessions. The CONTROL session
invoked no plugin command; the DRYRUN session invoked all six commands. Thus
the model DWG, Ortho DWG, `Piping.dcf`, project metadata, and other tracked
content-bearing files have no plugin-only delta in this comparison. Both
Launcher sessions completed automatic rollback to `RolledBack`.
@@ -1,5 +1,36 @@
# Ortho viewport membership
Every source snapshot has an explicit `ViewMembershipStatus`. The current Plant 3D 2026 adapter has no confirmed official object-to-view mapping API, so the seven source objects are `NotResolved` and skipped with `VIEW_MEMBERSHIP_NOT_RESOLVED`. Source presence is not treated as viewport membership.
## Runtime path
No geometric heuristic is used as an exact mapping. The next step requires a confirmed generated-entity linkage or documented view mapping.
`EntryPoint.RunDiagnostic` reads each `OrthoViewportInfo` and calls
`PlantObjectReader.ReadObjects`. The reader resolves source Piping rows, then
`PlantViewportMembershipReader.Read` evaluates the stored query for that exact
view. It maps `MdObjectId.PrjObjectId` to a source DataLinks row and assigns a
membership status to every independent `PlantObjectSnapshot`.
| Status | Meaning | Diagnostic evidence |
| --- | --- | --- |
| `Resolved` | The source row was returned by the exact view query. | `VIEW_MEMBERSHIP_FROM_CONFIRMED_PNP_ID` |
| `ObjectOutsideView` | The source row was not returned by that confirmed query. | `VIEW_MEMBERSHIP_OBJECT_OUTSIDE_CLIP` |
| `NotResolved` | The view contract is unavailable, ambiguous, or failed. | `VIEW_MEMBERSHIP_API_UNSUPPORTED` |
No source object is treated as visible solely because it belongs to the same
model drawing. No geometry-only result is upgraded to `Resolved`.
## Plan effect
`AnnotationPlanBuilder` includes membership status in its canonical plan input.
`ObjectOutsideView`, unsupported, and unresolved objects are `Skip` operations
with stable reason codes. `Resolved` membership can progress only through the
existing annotation rules; this DryRun slice never writes an annotation.
## Paired DryRun evidence
The paired local DryRun resolved five of seven source rows through the exact
view query and classified the remaining two as `ObjectOutsideView`. Each of
the three supported commands produced an identical PlanId on its repeated
invocation. Both Launcher sessions reached `RolledBack`.
The paired command-window DBMOD output was not retained. It is intentionally
reported as `NOT RECORDED`; project-file SHA-256 inventories nevertheless had
zero delta for both CONTROL and DRYRUN copies.
@@ -0,0 +1,56 @@
# Ortho viewport membership API
## Scope
This report records the Plant 3D 2026 contract used to map source-model
DataLinks objects to one Ortho view. Autodesk binaries were inspected in
`<PLANT3D_2026_INSTALL>` and are not part of this repository.
## Confirmed Plant 3D 2026 contract
| Assembly | Version | Type/member | Purpose |
| --- | --- | --- | --- |
| `PnPDwg2dDef` | `0.0.0.0` | `IDwgViewDef.DwgQuery` | Returns the query stored by an Ortho view. |
| `PnPDwg2dDef` | `0.0.0.0` | `DwgQueryByBox.Execute(StringCollection, StringCollection)` | Executes the stored view selection and returns `IDwgSet`. |
| `PnPDwg2dDef` | `0.0.0.0` | `IDwgSet.GetItem(int, ref MdObjectId)` | Enumerates selected source identities. |
| `PnPDwg2dUtil` | `0.0.0.0` | `MdObjectId.PrjObjectId` | Exposes the selected project object identity. |
| `PnPDataLinks` | `17.0.60.1` | `DataLinksManager.FindAcPpRowId(PpObjectId)` | Maps a selected project object to its DataLinks row. |
`DwgQueryByBox` also exposes `CoordinateSystem`, `XLength`, `YLength`,
`ZLength`, and `SelectionMask`. `DwgViewDef` exposes orientation, clipping,
and layout fields. They are not used as a heuristic in this slice because the
query result provides a stronger project-object identity.
## Selected implementation
`PlantViewportMembershipReader.Read` locates exactly one view by the stable
ID already generated from the sheet GUID and view name. It executes that
view's stored `DwgQuery`, converts every returned `MdObjectId.PrjObjectId`
through `DataLinksManager.FindAcPpRowId`, and intersects the result with the
source drawing's Piping rows.
Matching rows receive `Resolved` with
`VIEW_MEMBERSHIP_FROM_CONFIRMED_PNP_ID`. Source rows absent from the confirmed
query result receive `ObjectOutsideView` with
`VIEW_MEMBERSHIP_OBJECT_OUTSIDE_CLIP`. An unsupported or ambiguous runtime
contract stays `NotResolved` with `VIEW_MEMBERSHIP_API_UNSUPPORTED`; the code
does not guess alternate Autodesk properties or select an arbitrary view.
## Legacy analysis
The read-only legacy source at
`\\phpsrv03\NBIM\code\CS\P_Rec_Annot\v1.0.4\repo` was available. Its
`UpdateOrthoDrawings/Commands.cs` configured `DwgQuery.ModelFiles` and called
`DwgQuery.Execute`; `OrthoAutoAnnotate/Commands.cs` and
`AutoAnnotateView/ViewPorts.cs` also used viewport and entity extents. Those
extents checks are a geometric filter, not an authoritative object identity,
so they were not ported as `Resolved` membership.
## Side effects and failure handling
The reader opens no Autodesk write transaction and calls no persistence API.
It does not modify DWG, DCF, or project metadata. `DLException` while mapping
an individual generated identity is ignored only for that non-Piping identity;
other failures yield the stable API diagnostic and preserve no false positive.
Runtime DryRun and paired control/DryRun hash comparison remain required to
verify actual no-write behavior.
@@ -64,6 +64,15 @@ public sealed record PlantObjectSnapshot
public enum ViewMembershipStatus { NotResolved, Resolved, PartiallyResolved, ObjectOutsideView, Unsupported, Failed }
public sealed record SourceObjectViewportMembership
{
public required string SourceObjectStableKey { get; init; }
public required string ViewStableKey { get; init; }
public required ViewMembershipStatus Status { get; init; }
public IReadOnlyList<string> EvidenceCodes { get; init; } = Array.Empty<string>();
public string? ReasonCode { get; init; }
}
public sealed record PlantRuntimeContext
{
public bool HasActiveDocument { get; init; }
@@ -168,12 +177,40 @@ public sealed record AnnotationPlanningRequest
public required OrthoViewportInfo Viewport { get; init; }
public IReadOnlyList<PlantObjectSnapshot> Objects { get; init; } = Array.Empty<PlantObjectSnapshot>();
public IReadOnlyList<ExistingAnnotation> Existing { get; init; } = Array.Empty<ExistingAnnotation>();
// Test-only limited Apply intent. It is part of the canonical plan and is
// deliberately explicit: the planner must never choose an arbitrary Pipe.
public string? LimitedApplyTargetSnapshotId { get; init; }
public string? LimitedApplyRuleId { get; init; }
}
public interface IAnnotationPlanBuilder { AnnotationPlan Build(AnnotationPlanningRequest request); }
public enum PRecAnnotExecutionMode { Smoke, DryRun, Apply }
public interface IRuntimeWriteGuard { void EnsureWriteAllowed(string operation); }
public sealed record CreatePipeAnnotationOperation
{
public required string OperationId { get; init; }
public required string SourceObjectStableKey { get; init; }
public required string ViewStableKey { get; init; }
public required string RuleId { get; init; }
public required AnnotationPoint Placement { get; init; }
public required string DuplicateKey { get; init; }
}
public sealed record FirstLimitedApplyPermit
{
public required string FixtureMarker { get; init; }
public required bool AllowApply { get; init; }
public required string TargetSnapshotId { get; init; }
public required string ExpectedPlanId { get; init; }
}
public sealed record FirstLimitedApplyPreflight
{
public required string PlanId { get; init; }
public required CreatePipeAnnotationOperation Operation { get; init; }
}
public sealed record OrthoViewportRef(string DrawingId, string ViewportId);
public sealed record PlantObjectData
@@ -6,7 +6,7 @@ namespace PRecAnnot.Core;
public sealed class AnnotationPlanBuilder : IAnnotationPlanBuilder
{
public const int CurrentPlanSchemaVersion = 1;
public const int CurrentPlanSchemaVersion = 2;
public AnnotationPlan Build(AnnotationPlanningRequest request)
{
var objects = request.Objects.OrderBy(x => x.SnapshotId, StringComparer.Ordinal).ToArray();
@@ -31,9 +31,62 @@ public sealed class AnnotationPlanBuilder : IAnnotationPlanBuilder
continue;
}
if (!string.IsNullOrWhiteSpace(request.LimitedApplyTargetSnapshotId) &&
!string.Equals(item.SnapshotId, request.LimitedApplyTargetSnapshotId, StringComparison.Ordinal))
{
operations.Add(new AnnotationOperationPlan { SourceSnapshotId = item.SnapshotId, Kind = AnnotationOperationKind.Skip, Reasons = new[] { "APPLY_NOT_SELECTED_TARGET" } });
continue;
}
if (item.MembershipStatus is not ViewMembershipStatus.Resolved and not ViewMembershipStatus.PartiallyResolved)
{
operations.Add(new AnnotationOperationPlan { SourceSnapshotId = item.SnapshotId, Kind = AnnotationOperationKind.Skip, Reasons = new[] { "VIEW_MEMBERSHIP_NOT_RESOLVED" } });
operations.Add(new AnnotationOperationPlan { SourceSnapshotId = item.SnapshotId, Kind = AnnotationOperationKind.Skip, Reasons = new[] { MembershipReason(item.MembershipStatus) } });
continue;
}
// The first Apply slice is intentionally narrower than the
// historic bulk rules: exactly one explicitly selected, resolved
// Pipe can become a PIPE block operation. All other objects keep
// their normal diagnostic treatment.
if (!string.IsNullOrWhiteSpace(request.LimitedApplyTargetSnapshotId) &&
string.Equals(item.SnapshotId, request.LimitedApplyTargetSnapshotId, StringComparison.Ordinal))
{
if (!string.Equals(item.ObjectClass, "Pipe", StringComparison.OrdinalIgnoreCase))
{
operations.Add(new AnnotationOperationPlan { SourceSnapshotId = item.SnapshotId, Kind = AnnotationOperationKind.Unsupported, Reasons = new[] { "APPLY_UNSUPPORTED_OBJECT_CLASS" } });
continue;
}
if (item.MembershipStatus != ViewMembershipStatus.Resolved)
{
operations.Add(new AnnotationOperationPlan { SourceSnapshotId = item.SnapshotId, Kind = AnnotationOperationKind.Skip, Reasons = new[] { "APPLY_REQUIRES_RESOLVED_MEMBERSHIP" } });
continue;
}
if (item.Geometry.PaperPosition is not { } pipePosition)
{
operations.Add(new AnnotationOperationPlan { SourceSnapshotId = item.SnapshotId, Kind = AnnotationOperationKind.Skip, Reasons = new[] { "ANNOTATION_PLACEMENT_NOT_RESOLVED" } });
continue;
}
var pipeIntent = new AnnotationIntent
{
StableKey = $"{request.Viewport.Id}:{item.SnapshotId}:{FirstLimitedApplyPlanner.RuleId}",
Text = FirstLimitedApplyPlanner.RuleId,
Position = new AnnotationPoint(pipePosition.X, pipePosition.Y),
Rotation = item.Geometry.Rotation ?? 0,
ClassName = item.ObjectClass,
Layer = "PXP_ANNOTATION"
};
operations.Add(new AnnotationOperationPlan
{
SourceSnapshotId = item.SnapshotId,
Kind = AnnotationOperationKind.Create,
AnnotationType = "PIPE",
Text = FirstLimitedApplyPlanner.RuleId,
Position = pipePosition,
Rotation = item.Geometry.Rotation,
Reasons = Array.Empty<string>()
});
creates.Add(pipeIntent);
continue;
}
@@ -111,7 +164,9 @@ public sealed class AnnotationPlanBuilder : IAnnotationPlanBuilder
var canonical = new StringBuilder("schema=").Append(CurrentPlanSchemaVersion)
.Append("|command=").Append(Normalize(request.CommandName))
.Append("|source=").Append(Normalize(Path.GetFileName(request.Viewport.SourceDrawingName)))
.Append("|view=").Append(Normalize(request.Viewport.Id));
.Append("|view=").Append(Normalize(request.Viewport.Id))
.Append("|limited-target=").Append(Normalize(request.LimitedApplyTargetSnapshotId))
.Append("|limited-rule=").Append(Normalize(request.LimitedApplyRuleId));
foreach (var item in objects.OrderBy(x => x.SnapshotId, StringComparer.Ordinal))
{
canonical.Append("|object=").Append(Normalize(item.SnapshotId)).Append('|').Append(Normalize(item.ObjectClass))
@@ -127,4 +182,12 @@ public sealed class AnnotationPlanBuilder : IAnnotationPlanBuilder
}
private static string Normalize(string? value) => (value ?? string.Empty).Trim().Normalize(NormalizationForm.FormC);
private static string MembershipReason(ViewMembershipStatus status) => status switch
{
ViewMembershipStatus.ObjectOutsideView => "VIEW_MEMBERSHIP_OBJECT_OUTSIDE_CLIP",
ViewMembershipStatus.Unsupported => "VIEW_MEMBERSHIP_API_UNSUPPORTED",
ViewMembershipStatus.Failed => "VIEW_MEMBERSHIP_TRANSFORM_FAILED",
_ => "VIEW_MEMBERSHIP_NOT_RESOLVED"
};
}
@@ -0,0 +1,52 @@
using System.Security.Cryptography;
using System.Text;
using PRecAnnot.Abstractions;
namespace PRecAnnot.Core;
public sealed class FirstLimitedApplyPlanner
{
public const string FixtureMarker = "FIRST_LIMITED_APPLY";
public const string RuleId = "PXP-OA.PIPE.OPEN-END.V1";
public FirstLimitedApplyPreflight Prepare(
PRecAnnotExecutionMode mode,
AnnotationPlan plan,
OrthoViewportInfo viewport,
IReadOnlyList<PlantObjectSnapshot> objects,
FirstLimitedApplyPermit permit)
{
if (mode != PRecAnnotExecutionMode.Apply) throw new InvalidOperationException("APPLY_MODE_REQUIRED");
if (!permit.AllowApply || !string.Equals(permit.FixtureMarker, FixtureMarker, StringComparison.Ordinal))
throw new InvalidOperationException("APPLY_TEST_FIXTURE_OR_PERMIT_REQUIRED");
if (!string.Equals(permit.ExpectedPlanId, plan.PlanId, StringComparison.Ordinal))
throw new InvalidOperationException("APPLY_PLAN_STALE");
var targets = objects.Where(x => string.Equals(x.SnapshotId, permit.TargetSnapshotId, StringComparison.Ordinal)).ToArray();
if (targets.Length != 1) throw new InvalidOperationException("APPLY_REQUIRES_EXACTLY_ONE_TARGET");
var target = targets[0];
if (target.MembershipStatus != ViewMembershipStatus.Resolved) throw new InvalidOperationException("APPLY_REQUIRES_RESOLVED_MEMBERSHIP");
if (!string.Equals(target.ObjectClass, "Pipe", StringComparison.OrdinalIgnoreCase)) throw new InvalidOperationException("APPLY_UNSUPPORTED_OBJECT_CLASS");
if (target.Geometry.PaperPosition is not { } placement) throw new InvalidOperationException("ANNOTATION_PLACEMENT_NOT_RESOLVED");
var create = plan.Operations.Where(x => x.Kind == AnnotationOperationKind.Create && string.Equals(x.SourceSnapshotId, target.SnapshotId, StringComparison.Ordinal)).ToArray();
if (create.Length != 1) throw new InvalidOperationException("APPLY_TARGET_CREATE_NOT_PLANNED");
var duplicateKey = Stable("duplicate", RuleId, viewport.Id, target.SnapshotId);
return new FirstLimitedApplyPreflight
{
PlanId = plan.PlanId,
Operation = new CreatePipeAnnotationOperation
{
OperationId = Stable("operation", RuleId, viewport.Id, target.SnapshotId),
SourceObjectStableKey = target.SnapshotId,
ViewStableKey = viewport.Id,
RuleId = RuleId,
Placement = new AnnotationPoint(placement.X, placement.Y),
DuplicateKey = duplicateKey
}
};
}
private static string Stable(params string[] values) => Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(string.Join("|", values))))[..24];
}
@@ -0,0 +1,3 @@
using Autodesk.AutoCAD.Runtime;
[assembly: CommandClass(typeof(PRecAnnot.Plant3D2026.EntryPoint))]
@@ -14,6 +14,8 @@ public sealed class EntryPoint : IExtensionApplication
{
RuntimeDiagnostics.Write("AssemblyInitializeStarted");
RibbonIntegration.Register();
// Test automation is driven only by the explicitly generated /b script.
// Initialize remains production-equivalent and must not orchestrate commands.
RuntimeDiagnostics.Write("AssemblyInitializeCompleted");
}
@@ -33,6 +35,33 @@ public sealed class EntryPoint : IExtensionApplication
[CommandMethod("PXP-OA")]
public void OrthoAnnotateCommand() => RunDiagnostic("PXP-OA");
[CommandMethod("PHP_REC_AUTOMATION_START", CommandFlags.Session)]
public void AutomationStartCommand() => FirstLimitedApplyAutomationBootstrap.Arm();
// Test-only command-queue barrier. It deliberately performs no Plant or
// drawing work; its CommandEnded event proves the activated document is
// ready before the coordinator calls the Plant APIs.
[CommandMethod("PHP_REC_AUTOMATION_READY")]
public void AutomationReadyCommand() { }
[CommandMethod("PHP_REC_AUTOMATION_PREPARE", CommandFlags.Session)]
public void AutomationPrepareCommand() => ScriptDrivenFirstApplyAutomation.Prepare();
[CommandMethod("PHP_REC_AUTOMATION_BEFORE_PXP_1", CommandFlags.Session)]
public void AutomationBeforePxp1Command() => ScriptDrivenFirstApplyAutomation.RecordSentinel("BeforePxp1");
[CommandMethod("PHP_REC_AUTOMATION_AFTER_PXP_1", CommandFlags.Session)]
public void AutomationAfterPxp1Command() => ScriptDrivenFirstApplyAutomation.RecordSentinel("AfterPxp1");
[CommandMethod("PHP_REC_AUTOMATION_BEFORE_PXP_2", CommandFlags.Session)]
public void AutomationBeforePxp2Command() => ScriptDrivenFirstApplyAutomation.RecordSentinel("BeforePxp2");
[CommandMethod("PHP_REC_AUTOMATION_AFTER_PXP_2", CommandFlags.Session)]
public void AutomationAfterPxp2Command() => ScriptDrivenFirstApplyAutomation.RecordSentinel("AfterPxp2");
[CommandMethod("PHP_REC_AUTOMATION_FINALIZE", CommandFlags.Session)]
public void AutomationFinalizeCommand() => ScriptDrivenFirstApplyAutomation.FinalizeAutomation();
private static void RunDiagnostic(string commandName)
{
var operationId = Guid.NewGuid().ToString("N");
@@ -43,9 +72,14 @@ public sealed class EntryPoint : IExtensionApplication
{
document = AcadApplication.DocumentManager.MdiActiveDocument;
var mode = ParseMode(RuntimeDiagnostics.RuntimeMode);
if (mode == PRecAnnotExecutionMode.Apply) throw new InvalidOperationException("APPLY_MODE_NOT_IMPLEMENTED");
var context = new PlantRuntimeContextReader().ReadCurrentContext();
// Script-driven commands can run while Plant's implicit current
// document lags behind MDI activation. Bind the planner to the
// command document captured above, which is also the observer's
// fixture identity.
var context = ScriptDrivenFirstApplyAutomation.TryGetPreparedContext(document, out var scriptContext)
? scriptContext
: new PlantRuntimeContextReader().ReadCurrentContext(document);
var details = new Dictionary<string, object?>
{
["ExecutionMode"] = mode.ToString(),
@@ -75,6 +109,18 @@ public sealed class EntryPoint : IExtensionApplication
return;
}
if (mode == PRecAnnotExecutionMode.Apply && !string.Equals(commandName, "PXP-OA", StringComparison.Ordinal))
throw new InvalidOperationException("APPLY_RULE_NOT_SUPPORTED");
var hasLimitedApplyPermit = !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("P_REC_ANNOT_APPLY_PERMIT_PATH"));
var applyPermit = (mode == PRecAnnotExecutionMode.Apply || hasLimitedApplyPermit) && document is not null
? FirstLimitedApplyPermitReader.ReadFor(document)
: null;
var automationTarget = ScriptDrivenFirstApplyAutomation.SelectedTargetSnapshotId
?? FirstLimitedApplyAutomationCoordinator.SelectedTargetSnapshotId;
var limitedTarget = applyPermit?.TargetSnapshotId ?? automationTarget;
if (mode == PRecAnnotExecutionMode.Apply && context.Viewports.Count != 1)
throw new InvalidOperationException("APPLY_REQUIRES_EXACTLY_ONE_VIEWPORT");
var builder = new AnnotationPlanBuilder();
var plans = new List<AnnotationPlan>();
var objectsByViewport = new List<IReadOnlyList<PlantObjectSnapshot>>();
@@ -82,7 +128,26 @@ public sealed class EntryPoint : IExtensionApplication
{
var objects = new PlantObjectReader().ReadObjects(viewport, CancellationToken.None);
objectsByViewport.Add(objects);
plans.Add(builder.Build(new AnnotationPlanningRequest { CommandName = commandName, Viewport = viewport, Objects = objects }));
plans.Add(builder.Build(new AnnotationPlanningRequest
{
CommandName = commandName,
Viewport = viewport,
Objects = objects,
LimitedApplyTargetSnapshotId = limitedTarget,
LimitedApplyRuleId = limitedTarget is null ? null : FirstLimitedApplyPlanner.RuleId
}));
}
if (mode == PRecAnnotExecutionMode.DryRun &&
string.Equals(Environment.GetEnvironmentVariable("P_REC_ANNOT_DIAGNOSTICS"), "FirstLimitedApply", StringComparison.Ordinal))
{
var candidates = objectsByViewport.SelectMany(x => x)
.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();
WriteEditor(document, $"\nP_Rec_Annot Apply preview candidates: {string.Join(",", candidates)}");
details["ApplyPreviewCandidateCount"] = candidates.Length;
}
var summary = new
@@ -99,6 +164,7 @@ public sealed class EntryPoint : IExtensionApplication
var membershipResolved = objectsByViewport.SelectMany(x => x).Count(x => x.MembershipStatus == ViewMembershipStatus.Resolved);
var membershipPartial = objectsByViewport.SelectMany(x => x).Count(x => x.MembershipStatus == ViewMembershipStatus.PartiallyResolved);
var membershipUnresolved = objectsByViewport.SelectMany(x => x).Count(x => x.MembershipStatus == ViewMembershipStatus.NotResolved);
var membershipOutside = objectsByViewport.SelectMany(x => x).Count(x => x.MembershipStatus == ViewMembershipStatus.ObjectOutsideView);
var planId = plans.Count == 1 ? plans[0].PlanId : null;
details["ObjectsExamined"] = summary.ObjectsExamined;
details["PlanSchemaVersion"] = AnnotationPlanBuilder.CurrentPlanSchemaVersion;
@@ -106,20 +172,54 @@ public sealed class EntryPoint : IExtensionApplication
details["MembershipResolved"] = membershipResolved;
details["MembershipPartial"] = membershipPartial;
details["MembershipUnresolved"] = membershipUnresolved;
details["MembershipOutsideView"] = membershipOutside;
details["PlanCreateCount"] = summary.CreateCount;
details["PlanUpdateCount"] = summary.UpdateCount;
details["PlanSkipCount"] = summary.SkipCount;
details["PlanConflictCount"] = summary.ConflictCount;
details["PlanUnsupportedCount"] = summary.UnsupportedCount;
details["ErrorCount"] = summary.ErrorCount;
if (string.Equals(commandName, "PXP-OA", StringComparison.OrdinalIgnoreCase))
{
var candidates = objectsByViewport.SelectMany(x => x)
.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();
ScriptDrivenFirstApplyAutomation.RecordPxpOa(planId, candidates, context.Viewports.SingleOrDefault()?.Id, summary.CreateCount, summary.SkipCount, mode == PRecAnnotExecutionMode.Apply ? applyPermit is not null : null, summary.ObjectsExamined, membershipResolved);
FirstLimitedApplyAutomationCoordinator.RecordPxpOa(planId, candidates, context.Viewports.SingleOrDefault()?.Id, summary.CreateCount, summary.SkipCount, mode == PRecAnnotExecutionMode.Apply ? applyPermit is not null : null);
}
if (mode == PRecAnnotExecutionMode.Apply)
{
var corePermit = new FirstLimitedApplyPermit
{
FixtureMarker = FirstLimitedApplyPlanner.FixtureMarker,
AllowApply = applyPermit!.AllowApply,
TargetSnapshotId = applyPermit.TargetSnapshotId,
ExpectedPlanId = applyPermit.ExpectedPlanId
};
var preflight = new FirstLimitedApplyPlanner().Prepare(mode, plans.Single(), context.Viewports.Single(), objectsByViewport.Single(), corePermit);
new RuntimeWriteGuard(mode).EnsureWriteAllowed(preflight.Operation.OperationId);
RuntimeDiagnostics.Write("ApplyPreflightCompleted", operationId: operationId, commandName: commandName, resultStatus: "Accepted", document: document);
RuntimeDiagnostics.Write("AnnotationTransactionStarted", operationId: operationId, commandName: commandName, document: document);
var outcome = new FirstLimitedPipeAnnotationWriter().Write(document!, preflight.Operation, applyPermit.VisibilityState, applyPermit.AnnotationTemplateDwgPath);
ScriptDrivenFirstApplyAutomation.RecordApplyOutcome(outcome);
FirstLimitedApplyAutomationCoordinator.RecordApplyOutcome(outcome);
details["ApplyOutcome"] = outcome.ToString();
details["AnnotationsCreated"] = outcome == PipeAnnotationWriteOutcome.Created ? 1 : 0;
details["AnnotationsSkipped"] = outcome == PipeAnnotationWriteOutcome.AlreadyExists ? 1 : 0;
RuntimeDiagnostics.Write(outcome == PipeAnnotationWriteOutcome.Created ? "AnnotationCreated" : "AnnotationDuplicateDetected", operationId: operationId, commandName: commandName, resultStatus: outcome.ToString(), document: document);
RuntimeDiagnostics.Write("AnnotationTransactionCommitted", operationId: operationId, commandName: commandName, resultStatus: outcome.ToString(), document: document);
}
RuntimeDiagnostics.Write("CommandPreflightCompleted", operationId: operationId, commandName: commandName, resultStatus: "DryRunCompleted", document: document, details: details);
WriteEditor(document, $"\nP_Rec_Annot — диагностический план\nПроект: {context.ProjectDisplayName}\nТип чертежа: Ortho\nViewport: {context.Viewports.Count}\nОбъектов найдено: {summary.ObjectsExamined}\nБудет создано: {summary.CreateCount}\nБудет обновлено: {summary.UpdateCount}\nБудет пропущено: {summary.SkipCount}\nКонфликтов: {summary.ConflictCount}\nОшибок: {summary.ErrorCount}\nDWG не изменён.");
WriteEditor(document, $"\nPlan schema: {AnnotationPlanBuilder.CurrentPlanSchemaVersion}; PlanId: {planId ?? "multiple"}; membership resolved/partial/unresolved: {membershipResolved}/{membershipPartial}/{membershipUnresolved}.");
WriteUsage(operationId, commandName, "DryRunCompleted", document, details, stopwatch.ElapsedMilliseconds);
RuntimeDiagnostics.Write("CommandCompleted", operationId: operationId, commandName: commandName, resultStatus: "DryRunCompleted", document: document, details: details);
WriteEditor(document, $"\nPlan schema: {AnnotationPlanBuilder.CurrentPlanSchemaVersion}; PlanId: {planId ?? "multiple"}; membership resolved/partial/outside/unresolved: {membershipResolved}/{membershipPartial}/{membershipOutside}/{membershipUnresolved}.");
var completedStatus = mode == PRecAnnotExecutionMode.Apply ? "ApplyCompleted" : "DryRunCompleted";
WriteUsage(operationId, commandName, completedStatus, document, details, stopwatch.ElapsedMilliseconds);
RuntimeDiagnostics.Write("CommandCompleted", operationId: operationId, commandName: commandName, resultStatus: completedStatus, document: document, details: details);
}
catch (System.Exception exception)
{
if (string.Equals(commandName, "PXP-OA", StringComparison.OrdinalIgnoreCase) && string.Equals(Environment.GetEnvironmentVariable("P_REC_ANNOT_RUNTIME_MODE"), "Apply", StringComparison.OrdinalIgnoreCase))
ScriptDrivenFirstApplyAutomation.RecordApplyFailure(exception);
RuntimeDiagnostics.Write("CommandFailed", "ERROR", operationId, commandName, exception.Message, exception, document);
WriteUsage(operationId, commandName, exception.Message, document, null, stopwatch.ElapsedMilliseconds, exception);
WriteEditor(document, $"\nP_Rec_Annot: безопасная диагностика завершилась ошибкой: {exception.Message}");
@@ -0,0 +1,502 @@
using System.Text.Json;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.ProcessPower.PlantInstance;
using Autodesk.ProcessPower.ProjectManager;
using PRecAnnot.Abstractions;
using AcadApplication = Autodesk.AutoCAD.ApplicationServices.Application;
namespace PRecAnnot.Plant3D2026;
// Test-only coordinator. It is not registered in Ribbon and is inert unless
// the explicit NextPilot automation environment and local marker are present.
internal sealed record FirstApplyAutomationConfiguration
{
public required string Phase { get; init; }
public string? OrthoDrawingPath { get; init; }
public string? ModelDrawingPath { get; init; }
public required string EvidencePath { get; init; }
public required string FixtureMarkerPath { get; init; }
}
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
{
private enum BootstrapState { Created, Prepared, Armed, Starting, Completed }
private static BootstrapState state;
private static bool observedIdle;
private static int idleCallbackCount;
private static int coordinatorStartCount;
internal static int IdleCallbackCount => idleCallbackCount;
internal static int CoordinatorStartCount => coordinatorStartCount;
// Called from Initialize: BCL gates and one lightweight subscription only.
internal static void Prepare()
{
if (state != BootstrapState.Created) return;
state = BootstrapState.Prepared;
AcadApplication.Idle += OnIdle;
RuntimeDiagnostics.Write("AutomationBootstrapSubscribed", resultStatus: "Prepared");
}
// Public test command is intentionally side-effect free with respect to Plant APIs.
internal static void Arm()
{
if (state is BootstrapState.Prepared)
{
// The command itself is the post-NETLOAD barrier. The next Idle
// callback, not this command callback, is allowed to start work.
observedIdle = true;
state = BootstrapState.Armed;
}
}
private static void OnIdle(object? sender, EventArgs args)
{
try
{
idleCallbackCount++;
RuntimeDiagnostics.Write("AutomationBootstrapIdle", resultStatus: state.ToString());
// The script engine can consume the custom command after NETLOAD. The
// first idle is therefore the explicit post-NETLOAD arming point.
if (state == BootstrapState.Prepared) { observedIdle = true; state = BootstrapState.Armed; }
if (!observedIdle || state != BootstrapState.Armed) return;
state = BootstrapState.Starting;
AcadApplication.Idle -= OnIdle;
coordinatorStartCount++;
RuntimeDiagnostics.Write("AutomationCoordinatorStarting", resultStatus: "FirstPostStartupIdle");
new FirstLimitedApplyAutomationCoordinator().Start();
}
catch (System.Exception exception)
{
state = BootstrapState.Completed;
AcadApplication.Idle -= OnIdle;
FirstLimitedApplyAutomationCoordinator.WriteBootstrapFailure(exception);
}
}
}
internal sealed record FirstApplyAutomationEvidence
{
public int SchemaVersion { get; init; } = 1;
public required string Phase { get; init; }
public bool Completed { get; init; }
public bool? Prepared { get; init; }
public bool? FunctionalEvidenceCompleted { get; init; }
public string? ErrorCode { get; init; }
public int? DbmodBefore { get; init; }
public int? DbmodAfterCommand1 { get; init; }
public int? DbmodAfterCommand2 { get; init; }
public int? DbmodAfterUndo { get; init; }
public AutomationEntityCounts? Baseline { get; init; }
public AutomationEntityCounts? AfterCommand1 { get; init; }
public AutomationEntityCounts? AfterCommand2 { get; init; }
public AutomationEntityCounts? AfterUndo { get; init; }
public IReadOnlyList<string> CompletedCommands { get; init; } = Array.Empty<string>();
public string? PlanIdRun1 { get; init; }
public string? PlanIdRun2 { get; init; }
public bool? PlanIdsEqual { get; init; }
public int? EligibleCandidateCount { get; init; }
public string? SelectedCandidateHash { get; init; }
public string? ViewHash { get; init; }
public int? CreateRun1 { get; init; }
public int? CreateRun2 { get; init; }
public int? SkipRun2 { get; init; }
public string? ApplyOutcomeRun1 { get; init; }
public string? ApplyOutcomeRun2 { get; init; }
public bool? PermitAccepted { get; init; }
public bool? CommandsCompleted { get; init; }
public bool? InspectorsCompleted { get; init; }
public bool? EvidenceFlushed { get; init; }
public bool? ExitAttemptQueued { get; init; }
public bool? QuitCommandWillStart { get; init; }
public bool? QuitCommandEnded { get; init; }
public string? ExitFailureCode { get; init; }
public int? IdleCallbackCount { get; init; }
public int? CoordinatorStartCount { get; init; }
// Local test evidence only. This is intentionally never copied into Git
// evidence because exception stacks may contain machine-specific paths.
public string? ExceptionText { get; init; }
public string? PreviousState { get; init; }
public string? CurrentState { get; init; }
public IReadOnlyList<string> StateTransitions { get; init; } = Array.Empty<string>();
public PxpOaDiscoveryEvidence? PxpOaDiscovery { get; init; }
public IReadOnlyList<AutomationSentinelEvidence> Sentinels { get; init; } = Array.Empty<AutomationSentinelEvidence>();
public IReadOnlyList<string> CommandEvents { get; init; } = Array.Empty<string>();
public IReadOnlyList<AutomationCommandCorrelationEvidence> CommandCorrelations { get; init; } = Array.Empty<AutomationCommandCorrelationEvidence>();
}
internal sealed class FirstLimitedApplyAutomationCoordinator
{
private enum CoordinatorState { Created, Starting, Running, Completed, Failed, ExitQueued }
private sealed record PxpResult(string? PlanId, IReadOnlyList<string> Candidates, string? View, int Create, int Skip, bool? PermitAccepted, string? ApplyOutcome);
private static string? selectedTargetSnapshotId;
private static readonly List<PxpResult> pxpResults = [];
internal static string? SelectedTargetSnapshotId => selectedTargetSnapshotId;
internal static void WriteBootstrapFailure(System.Exception exception) =>
RuntimeDiagnostics.Write("AutomationBootstrapFailed", "ERROR", resultStatus: "AUTOMATION_BOOTSTRAP_FAILED", exception: exception);
internal static void RecordPxpOa(string? planId, IReadOnlyList<string> candidates, string? view, int create, int skip, bool? permitAccepted)
{
if (string.Equals(Environment.GetEnvironmentVariable("P_REC_ANNOT_AUTOMATION"), "FirstLimitedApplyRuntimeTest", StringComparison.Ordinal))
pxpResults.Add(new(planId, candidates, view, create, skip, permitAccepted, null));
}
internal static void RecordApplyOutcome(PipeAnnotationWriteOutcome outcome)
{
if (!string.Equals(Environment.GetEnvironmentVariable("P_REC_ANNOT_AUTOMATION"), "FirstLimitedApplyRuntimeTest", StringComparison.Ordinal) || pxpResults.Count == 0) return;
var last = pxpResults[^1];
pxpResults[^1] = last with { ApplyOutcome = outcome.ToString() };
}
private FirstApplyAutomationConfiguration? config;
private Document? document;
private readonly List<Document> automationOpenedDocuments = [];
private readonly List<string> completed = [];
private int step;
private int? before, after1, after2, afterUndo;
private AutomationEntityCounts? baseline, counts1, counts2, countsUndo;
private readonly List<string> transitions = ["Created"];
private CoordinatorState state = CoordinatorState.Created;
private CoordinatorState previousState = CoordinatorState.Created;
private bool documentHandlersSubscribed;
private bool lifecycleHandlersSubscribed;
private bool fixtureDocumentActivated;
private bool projectActivationStarted;
private bool exitQueued;
private bool exitHandlerSubscribed;
private string? exceptionText;
private bool functionalCompleted;
private bool evidenceFlushed;
private bool exitAttemptQueued;
private string? exitFailureCode;
public void Start()
{
try
{
Transition(CoordinatorState.Starting, "PostStartupReady");
config = ReadConfiguration();
if (string.Equals(config.Phase, "LifecycleProbe", StringComparison.OrdinalIgnoreCase))
{
CompleteLifecycleProbe();
return;
}
if (string.IsNullOrWhiteSpace(config.OrthoDrawingPath)) throw new InvalidOperationException("AUTOMATION_ORTHO_PATH_REQUIRED");
// A first startup Idle is sufficient to arm automation, but not to
// call Plant APIs. Establish an AutoCAD document first and wait for
// its activation plus a quiescent callback before project activation.
AcadApplication.DocumentManager.DocumentActivated += OnDocumentActivated;
AcadApplication.Idle += OnIdle;
lifecycleHandlersSubscribed = true;
if (!string.IsNullOrWhiteSpace(config.ModelDrawingPath)) automationOpenedDocuments.Add(AcadApplication.DocumentManager.Open(config.ModelDrawingPath, false));
transitions.Add("DocumentOpenStarted");
RuntimeDiagnostics.Write("AutomationBeforeDocumentOpen", resultStatus: "PostStartupReady");
document = AcadApplication.DocumentManager.Open(config.OrthoDrawingPath, false);
automationOpenedDocuments.Add(document);
AcadApplication.DocumentManager.MdiActiveDocument = document;
transitions.Add("DocumentOpened");
RuntimeDiagnostics.Write("AutomationAfterDocumentOpen", resultStatus: "WaitingForDocumentActivation", document: document);
document.CommandEnded += OnCommandEnded;
document.CommandFailed += OnCommandFailed;
document.CommandCancelled += OnCommandCancelled;
documentHandlersSubscribed = true;
Transition(CoordinatorState.Running, "AutomationRunning");
// DocumentActivated can be raised synchronously by Open before the
// coordinator receives the returned Document. Confirm the current
// active document after assignment so that one missed event cannot
// strand the workflow waiting for another startup Idle.
// Open was requested for this exact local fixture and it has just
// been made MDI-active. Queue a no-op public command regardless of
// wrapper identity/name normalisation; CommandEnded is the actual
// readiness proof before any Plant API is used.
fixtureDocumentActivated = true;
transitions.Add("FixtureDocumentActiveAfterOpen");
QueueReadinessBarrier();
}
catch (System.Exception ex) { FailFromCallback("Start", ex, "AUTOMATION_START_FAILED"); }
}
private void CompleteLifecycleProbe()
{
transitions.Add("LifecycleProbeCompleted");
WriteEvidence(true, null);
// LifecycleProbe is already running from the first verified Idle callback.
// Do not wait for a second Idle: a startup-script host is permitted not to
// raise one, which otherwise leaves acad.exe responsive but stranded.
RequestControlledExit(idleSafe: true);
}
private void OnIdle(object? sender, EventArgs e) => RunCallback("Idle", TryActivateProjectAndVerifyContext);
private void OnDocumentActivated(object sender, DocumentCollectionEventArgs e)
{
RunCallback("DocumentActivated", () =>
{
if (document is null || e.Document is null) return;
if (!string.Equals(e.Document.Name, document.Name, StringComparison.OrdinalIgnoreCase)) return;
fixtureDocumentActivated = true;
transitions.Add("FixtureDocumentActivated");
TryActivateProjectAndVerifyContext();
});
}
private void TryActivateProjectAndVerifyContext()
{
if (IsTerminal || document is null || !fixtureDocumentActivated || projectActivationStarted) return;
if (!AcadApplication.IsQuiescent) return;
projectActivationStarted = true;
AcadApplication.Idle -= OnIdle;
AcadApplication.DocumentManager.DocumentActivated -= OnDocumentActivated;
lifecycleHandlersSubscribed = false;
try
{
transitions.Add("ProjectActivationStarted");
RuntimeDiagnostics.Write("AutomationBeforeProjectActivation", resultStatus: "DocumentActivatedAndQuiescent", document: document);
var projectXml = Path.Combine(Path.GetDirectoryName(config!.FixtureMarkerPath)!, "Project.xml");
var project = PlantProject.LoadProject(projectXml, false, string.Empty, string.Empty);
PlantApplication.SetCurrentProject(project, true);
transitions.Add("ProjectActivated");
RuntimeDiagnostics.Write("AutomationAfterProjectActivation", resultStatus: "ProjectAvailable", document: document);
transitions.Add("ContextVerificationStarted");
var context = new PlantRuntimeContextReader().ReadCurrentContext(document);
if (!context.HasPlantProject || !context.IsOrthoDrawing || context.Viewports.Count != 1) { Complete(false, $"AUTOMATION_ORTHO_CONTEXT_INVALID:P={context.HasPlantProject};O={context.IsOrthoDrawing};V={context.Viewports.Count}"); return; }
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) { Complete(false, "AUTOMATION_NO_ELIGIBLE_CANDIDATE"); return; }
selectedTargetSnapshotId = candidates[0];
transitions.Add("ContextVerified");
before = Dbmod(); baseline = Counts();
Queue("PXP-OA");
}
catch (System.Exception ex) { FailFromCallback("ContextVerification", ex, "AUTOMATION_CONTEXT_FAILED"); }
}
private void Queue(string command) => _ = DispatchCommandAsync(command, false);
private void QueueReadinessBarrier()
{
_ = DispatchCommandAsync("PHP_REC_AUTOMATION_READY", true);
}
private async Task DispatchCommandAsync(string command, bool readiness)
{
if (IsTerminal || document is null) return;
var expected = document;
transitions.Add(readiness ? "ReadinessCommandDispatchRequested" : "CommandDispatchRequested:" + command);
try
{
await AcadApplication.DocumentManager.ExecuteInCommandContextAsync(async _ =>
{
transitions.Add("CommandContextEntered:" + command);
var active = AcadApplication.DocumentManager.MdiActiveDocument;
if (active is null || !string.Equals(active.Name, expected.Name, StringComparison.OrdinalIgnoreCase))
throw new InvalidOperationException("AUTOMATION_COMMAND_DOCUMENT_NOT_ACTIVE");
transitions.Add(readiness ? "ReadinessBarrierQueued" : "CommandQueued:" + command);
await active.Editor.CommandAsync(command);
transitions.Add("CommandInvocationReturned:" + command);
}, null);
}
catch (System.Exception exception) { FailFromCallback("CommandDispatcher", exception, "AUTOMATION_COMMAND_CONTEXT_FAILED"); }
}
private void OnCommandEnded(object sender, CommandEventArgs e)
{
RunCallback("CommandEnded", () =>
{
var name = e.GlobalCommandName?.Trim().TrimStart('.', '_').ToUpperInvariant();
if (name == "PHP_REC_AUTOMATION_READY")
{
transitions.Add("ReadinessBarrierCompleted");
TryActivateProjectAndVerifyContext();
return;
}
if (name is not ("PXP-OA" or "UNDO" or "U")) return;
completed.Add(name);
transitions.Add("CommandCompleted:" + name);
if (step++ == 0) { after1 = Dbmod(); counts1 = Counts(); Queue("PXP-OA"); return; }
if (step == 2 && string.Equals(config?.Phase, "Apply", StringComparison.OrdinalIgnoreCase)) { after2 = Dbmod(); counts2 = Counts(); Queue("_.UNDO _1"); return; }
if (string.Equals(config?.Phase, "Apply", StringComparison.OrdinalIgnoreCase)) { afterUndo = Dbmod(); countsUndo = Counts(); Complete(true, null, safeToExitNow: true); }
else { after2 = Dbmod(); counts2 = Counts(); Complete(true, null, safeToExitNow: true); }
});
}
private void OnCommandFailed(object sender, CommandEventArgs e) => RunCallback("CommandFailed", () => Complete(false, "AUTOMATION_COMMAND_FAILED:" + e.GlobalCommandName));
private void OnCommandCancelled(object sender, CommandEventArgs e) => RunCallback("CommandCancelled", () => Complete(false, "AUTOMATION_COMMAND_CANCELLED:" + e.GlobalCommandName));
private int Dbmod() => Convert.ToInt32(AcadApplication.GetSystemVariable("DBMOD"));
private AutomationEntityCounts Counts()
{
if (document is null) throw new InvalidOperationException("AUTOMATION_DOCUMENT_UNAVAILABLE");
using var tx = document.Database.TransactionManager.StartOpenCloseTransaction();
var table = (BlockTable)tx.GetObject(document.Database.BlockTableId, OpenMode.ForRead);
var paper = (BlockTableRecord)tx.GetObject(table[BlockTableRecord.PaperSpace], OpenMode.ForRead);
var total = 0; var pipes = 0; var xdata = 0; var owned = 0;
foreach (ObjectId id in paper)
{
total++;
if (tx.GetObject(id, OpenMode.ForRead) is BlockReference block && string.Equals(block.Name, "PIPE", StringComparison.OrdinalIgnoreCase)) pipes++;
if (tx.GetObject(id, OpenMode.ForRead) is Entity entity)
{
using var data = entity.GetXDataForApplication("PXP-OA");
if (data is not null) { xdata++; if (data.AsArray().Any(x => x.TypeCode == 1000)) owned++; }
}
}
return new(total, pipes, xdata, owned);
}
private FirstApplyAutomationConfiguration ReadConfiguration()
{
if (!string.Equals(Environment.GetEnvironmentVariable("P_REC_ANNOT_AUTOMATION"), "FirstLimitedApplyRuntimeTest", StringComparison.Ordinal)) throw new InvalidOperationException("AUTOMATION_DISABLED");
if (!string.Equals(RuntimeDiagnostics.RuntimeMode, "DryRun", StringComparison.OrdinalIgnoreCase) && !string.Equals(RuntimeDiagnostics.RuntimeMode, "Apply", StringComparison.OrdinalIgnoreCase)) throw new InvalidOperationException("AUTOMATION_MODE_BLOCKED");
var path = Environment.GetEnvironmentVariable("P_REC_ANNOT_AUTOMATION_CONFIG");
if (string.IsNullOrWhiteSpace(path) || !Path.IsPathFullyQualified(path) || path.StartsWith("\\\\", StringComparison.Ordinal)) throw new InvalidOperationException("AUTOMATION_CONFIGURATION_REQUIRED");
var loaded = JsonSerializer.Deserialize<FirstApplyAutomationConfiguration>(File.ReadAllText(path), new JsonSerializerOptions { PropertyNameCaseInsensitive = true }) ?? throw new InvalidOperationException("AUTOMATION_CONFIGURATION_INVALID");
if (!File.Exists(loaded.FixtureMarkerPath) || !string.Equals(File.ReadAllText(loaded.FixtureMarkerPath).Trim(), "P_REC_ANNOT_TEST_FIXTURE=FIRST_LIMITED_APPLY", StringComparison.Ordinal)) throw new InvalidOperationException("AUTOMATION_FIXTURE_MARKER_INVALID");
if (!string.Equals(loaded.Phase, "LifecycleProbe", StringComparison.OrdinalIgnoreCase) &&
(string.IsNullOrWhiteSpace(loaded.OrthoDrawingPath) || !Path.GetFullPath(loaded.OrthoDrawingPath).StartsWith(Path.GetDirectoryName(Path.GetFullPath(loaded.FixtureMarkerPath))! + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)))
throw new InvalidOperationException("AUTOMATION_FIXTURE_OUTSIDE_MARKER_ROOT");
return loaded;
}
private void Complete(bool success, string? error, bool safeToExitNow = false)
{
if (IsTerminal) return;
functionalCompleted = success;
Transition(success ? CoordinatorState.Completed : CoordinatorState.Failed, success ? "Completed" : "Failed");
UnsubscribeHandlers();
TryWriteEvidence(success, error);
selectedTargetSnapshotId = null;
pxpResults.Clear();
RequestControlledExit(safeToExitNow);
}
// The managed Application.Quit call can leave a startup-script host alive
// when it is invoked from a CommandEnded callback. Queue AutoCAD's native
// QUIT command instead; the command processor runs it after the current
// public PXP-OA command has fully returned and discards every open drawing.
private void RequestControlledExit(bool idleSafe = false)
{
if (exitQueued) return;
exitQueued = true;
Transition(CoordinatorState.ExitQueued, "ExitQueued");
if (idleSafe)
{
DispatchNativeQuit();
return;
}
AcadApplication.Idle += OnExitIdle;
exitHandlerSubscribed = true;
}
private void OnExitIdle(object? sender, EventArgs args)
{
RunCallback("ControlledExitIdle", DispatchNativeQuit);
}
private void DispatchNativeQuit()
{
try
{
if (exitHandlerSubscribed) AcadApplication.Idle -= OnExitIdle;
exitHandlerSubscribed = false;
exitAttemptQueued = true;
if (!AcadApplication.IsQuiescent)
{
transitions.Add("QuitDispatchDeferredNotQuiescent");
TryWriteEvidence(functionalCompleted, null);
return;
}
// LifecycleProbe opens no fixture document, but the real startup
// document is available at this point (observed as Drawing1.dwg).
// Use that live document only to queue native QUIT; never retain it
// past this callback or use it for fixture/Plant work.
var exitDocument = AcadApplication.DocumentManager.MdiActiveDocument;
if (exitDocument is null)
{
exitFailureCode = "AUTOMATION_EXIT_DOCUMENT_UNAVAILABLE";
transitions.Add("QuitDispatchSkippedNoLiveDocument");
TryWriteEvidence(functionalCompleted, null);
return;
}
transitions.Add("QuitDispatchAttempted");
// Persist the exit boundary before handing control back to AutoCAD;
// process termination can otherwise prevent the final evidence flush.
TryWriteEvidence(functionalCompleted, null);
exitDocument.SendStringToExecute("_.QUIT _N\n", true, true, false);
document = null;
automationOpenedDocuments.Clear();
}
catch (System.Exception exception)
{
// This is terminal cleanup: contain the exception and leave process
// cleanup to Launcher rather than re-entering document APIs.
// A failed graceful-exit dispatch is not a failure of already
// completed PXP-OA/UNDO work. Launcher owns bounded forced cleanup.
exitFailureCode = "AUTOMATION_CONTROLLED_EXIT_FAILED:" + exception.GetType().Name;
transitions.Add("QuitDispatchFailed");
TryWriteEvidence(functionalCompleted, null);
}
}
private bool IsTerminal => state is CoordinatorState.Completed or CoordinatorState.Failed or CoordinatorState.ExitQueued;
private void Transition(CoordinatorState next, string transition)
{
previousState = state;
state = next;
transitions.Add(transition);
}
private void RunCallback(string callback, Action action)
{
if (IsTerminal && callback != "ControlledExitIdle") return;
try { action(); }
catch (System.Exception exception) { FailFromCallback(callback, exception, "AUTOMATION_CALLBACK_FAILED"); }
}
private void FailFromCallback(string callback, System.Exception exception, string code)
{
exceptionText = exception.ToString();
RuntimeDiagnostics.Write("AutomationCallbackFailed", "ERROR", resultStatus: code + ":" + callback, exception: exception);
if (!IsTerminal) Complete(false, code + ":" + callback + ":" + exception.GetType().Name);
else
{
UnsubscribeHandlers();
if (functionalCompleted)
{
exitFailureCode = code + ":" + callback + ":" + exception.GetType().Name;
transitions.Add("ExitCallbackFailed");
TryWriteEvidence(true, null);
}
else TryWriteEvidence(false, code + ":" + callback + ":" + exception.GetType().Name);
}
}
private void UnsubscribeHandlers()
{
if (documentHandlersSubscribed && document is not null)
{
try { document.CommandEnded -= OnCommandEnded; document.CommandFailed -= OnCommandFailed; document.CommandCancelled -= OnCommandCancelled; }
catch (System.Exception exception) { RuntimeDiagnostics.Write("AutomationHandlerUnsubscribeFailed", "ERROR", resultStatus: "AUTOMATION_UNSUBSCRIBE_FAILED", exception: exception); }
}
documentHandlersSubscribed = false;
if (lifecycleHandlersSubscribed)
{
try { AcadApplication.DocumentManager.DocumentActivated -= OnDocumentActivated; AcadApplication.Idle -= OnIdle; }
catch (System.Exception exception) { RuntimeDiagnostics.Write("AutomationHandlerUnsubscribeFailed", "ERROR", resultStatus: "AUTOMATION_UNSUBSCRIBE_FAILED", exception: exception); }
}
lifecycleHandlersSubscribed = false;
}
private bool TryWriteEvidence(bool success, string? error)
{
try { evidenceFlushed = true; WriteEvidence(success, error); return true; }
catch (System.Exception exception) { evidenceFlushed = false; RuntimeDiagnostics.Write("AutomationEvidenceWriteFailed", "ERROR", resultStatus: "AUTOMATION_EVIDENCE_WRITE_FAILED", exception: exception); return false; }
}
private void WriteEvidence(bool success, string? error)
{
if (config is null) return;
var first = pxpResults.ElementAtOrDefault(0); var second = pxpResults.ElementAtOrDefault(1);
var expectedCommands = string.Equals(config.Phase, "Apply", StringComparison.OrdinalIgnoreCase) ? 3 : 2;
var evidence = new FirstApplyAutomationEvidence { Phase = config.Phase, Completed = success, ErrorCode = error, DbmodBefore = before, DbmodAfterCommand1 = after1, DbmodAfterCommand2 = after2, DbmodAfterUndo = afterUndo, Baseline = baseline, AfterCommand1 = counts1, AfterCommand2 = counts2, AfterUndo = countsUndo, CompletedCommands = completed, 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 = selectedTargetSnapshotId, ViewHash = first?.View, CreateRun1 = first?.Create, CreateRun2 = second?.Create, SkipRun2 = second?.Skip, ApplyOutcomeRun1 = first?.ApplyOutcome, ApplyOutcomeRun2 = second?.ApplyOutcome, PermitAccepted = first?.PermitAccepted, CommandsCompleted = completed.Count >= expectedCommands, InspectorsCompleted = baseline is not null && counts1 is not null && counts2 is not null && (!string.Equals(config.Phase, "Apply", StringComparison.OrdinalIgnoreCase) || countsUndo is not null), EvidenceFlushed = evidenceFlushed, ExitAttemptQueued = exitAttemptQueued, ExitFailureCode = exitFailureCode, IdleCallbackCount = FirstLimitedApplyAutomationBootstrap.IdleCallbackCount, CoordinatorStartCount = FirstLimitedApplyAutomationBootstrap.CoordinatorStartCount, ExceptionText = exceptionText, PreviousState = previousState.ToString(), CurrentState = state.ToString(), StateTransitions = transitions };
Directory.CreateDirectory(Path.GetDirectoryName(config.EvidencePath)!);
File.WriteAllText(config.EvidencePath, JsonSerializer.Serialize(evidence));
}
}
@@ -0,0 +1,140 @@
using System.Text.Json;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using PRecAnnot.Abstractions;
namespace PRecAnnot.Plant3D2026;
// This file is deliberately runtime-only. Core receives only the anonymised
// permit and canonical operation; no AutoCAD handle, database or project path
// crosses the adapter boundary.
internal sealed record FirstLimitedApplyRuntimePermit
{
public required bool AllowApply { get; init; }
public required string FixtureMarkerPath { get; init; }
public required string TargetSnapshotId { get; init; }
public required string ExpectedPlanId { get; init; }
public required string VisibilityState { get; init; }
// Test-only local copy of the legacy v1.0.4 Template.dwg. The package
// deliberately carries no DWG assets; the per-run permit binds this source.
public required string AnnotationTemplateDwgPath { get; init; }
}
internal static class FirstLimitedApplyPermitReader
{
private const string MarkerContent = "P_REC_ANNOT_TEST_FIXTURE=FIRST_LIMITED_APPLY";
public static FirstLimitedApplyRuntimePermit ReadFor(Document document)
{
var path = Environment.GetEnvironmentVariable("P_REC_ANNOT_APPLY_PERMIT_PATH");
if (string.IsNullOrWhiteSpace(path) || !Path.IsPathFullyQualified(path) || path.StartsWith("\\\\", StringComparison.Ordinal))
throw new InvalidOperationException("APPLY_PERMIT_PATH_REQUIRED");
if (!File.Exists(path)) throw new InvalidOperationException("APPLY_PERMIT_MISSING");
var permit = JsonSerializer.Deserialize<FirstLimitedApplyRuntimePermit>(File.ReadAllText(path), new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
if (permit is null || !permit.AllowApply || string.IsNullOrWhiteSpace(permit.TargetSnapshotId) || string.IsNullOrWhiteSpace(permit.ExpectedPlanId) || string.IsNullOrWhiteSpace(permit.VisibilityState))
throw new InvalidOperationException("APPLY_TEST_FIXTURE_OR_PERMIT_REQUIRED");
if (!Path.IsPathFullyQualified(permit.FixtureMarkerPath) || permit.FixtureMarkerPath.StartsWith("\\\\", StringComparison.Ordinal) || !File.Exists(permit.FixtureMarkerPath))
throw new InvalidOperationException("APPLY_TEST_FIXTURE_OR_PERMIT_REQUIRED");
if (!string.Equals(File.ReadAllText(permit.FixtureMarkerPath).Trim(), MarkerContent, StringComparison.Ordinal))
throw new InvalidOperationException("APPLY_TEST_FIXTURE_OR_PERMIT_REQUIRED");
var root = Path.GetDirectoryName(Path.GetFullPath(permit.FixtureMarkerPath))!;
var drawing = Path.GetFullPath(document.Name);
if (!drawing.StartsWith(root + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase))
throw new InvalidOperationException("APPLY_TEST_FIXTURE_OR_PERMIT_REQUIRED");
if (string.IsNullOrWhiteSpace(permit.AnnotationTemplateDwgPath) || !Path.IsPathFullyQualified(permit.AnnotationTemplateDwgPath) || permit.AnnotationTemplateDwgPath.StartsWith("\\\\", StringComparison.Ordinal) || !File.Exists(permit.AnnotationTemplateDwgPath))
throw new InvalidOperationException("ANNOTATION_TEMPLATE_ASSET_REQUIRED");
return permit;
}
}
internal enum PipeAnnotationWriteOutcome { Created, AlreadyExists }
internal sealed class FirstLimitedPipeAnnotationWriter
{
private const string BlockName = "PIPE";
private const string XdataApplication = "PXP-OA";
public PipeAnnotationWriteOutcome Write(Document document, CreatePipeAnnotationOperation operation, string visibilityState, string annotationTemplateDwgPath)
{
var database = document.Database;
using var transaction = database.TransactionManager.StartTransaction();
var table = (BlockTable)transaction.GetObject(database.BlockTableId, OpenMode.ForRead);
var blockId = EnsureLegacyPipeBlock(transaction, database, table, annotationTemplateDwgPath);
var paper = (BlockTableRecord)transaction.GetObject(table[BlockTableRecord.PaperSpace], OpenMode.ForWrite);
if (HasOwnedAnnotation(transaction, paper, operation.DuplicateKey)) return PipeAnnotationWriteOutcome.AlreadyExists;
EnsureRegApp(transaction, database);
using var annotation = new BlockReference(
new Point3d(operation.Placement.X, operation.Placement.Y, operation.Placement.Z),
blockId);
paper.AppendEntity(annotation);
transaction.AddNewlyCreatedDBObject(annotation, true);
SetConfirmedVisibility(annotation, visibilityState);
annotation.XData = new ResultBuffer(
new TypedValue((int)DxfCode.ExtendedDataRegAppName, XdataApplication),
new TypedValue((int)DxfCode.ExtendedDataAsciiString, operation.DuplicateKey),
new TypedValue((int)DxfCode.ExtendedDataAsciiString, operation.RuleId));
transaction.Commit();
return PipeAnnotationWriteOutcome.Created;
}
private static ObjectId EnsureLegacyPipeBlock(Transaction destinationTransaction, Database destination, BlockTable destinationTable, string templatePath)
{
if (destinationTable.Has(BlockName)) return destinationTable[BlockName];
if (!File.Exists(templatePath)) throw new InvalidOperationException("ANNOTATION_TEMPLATE_ASSET_MISSING");
using var source = new Database(false, true);
source.ReadDwgFile(templatePath, FileShare.Read, true, string.Empty);
source.CloseInput(true);
using var sourceTransaction = source.TransactionManager.StartTransaction();
var sourceTable = (BlockTable)sourceTransaction.GetObject(source.BlockTableId, OpenMode.ForRead);
if (!sourceTable.Has(BlockName)) throw new InvalidOperationException("ANNOTATION_TEMPLATE_PIPE_BLOCK_MISSING");
using var sourceIds = new ObjectIdCollection { sourceTable[BlockName] };
using var mapping = new IdMapping();
destination.WblockCloneObjects(sourceIds, destination.BlockTableId, mapping, DuplicateRecordCloning.Ignore, false);
sourceTransaction.Commit();
var refreshed = (BlockTable)destinationTransaction.GetObject(destination.BlockTableId, OpenMode.ForRead);
if (!refreshed.Has(BlockName)) throw new InvalidOperationException("ANNOTATION_TEMPLATE_BLOCK_IMPORT_FAILED");
return refreshed[BlockName];
}
private static bool HasOwnedAnnotation(Transaction transaction, BlockTableRecord paper, string duplicateKey)
{
foreach (ObjectId id in paper)
{
if (transaction.GetObject(id, OpenMode.ForRead) is not BlockReference reference) continue;
using var xdata = reference.GetXDataForApplication(XdataApplication);
if (xdata?.AsArray().Any(value => value.TypeCode == (int)DxfCode.ExtendedDataAsciiString && string.Equals(value.Value as string, duplicateKey, StringComparison.Ordinal)) == true)
return true;
}
return false;
}
private static void EnsureRegApp(Transaction transaction, Database database)
{
var apps = (RegAppTable)transaction.GetObject(database.RegAppTableId, OpenMode.ForRead);
if (apps.Has(XdataApplication)) return;
apps.UpgradeOpen();
using var record = new RegAppTableRecord { Name = XdataApplication };
apps.Add(record);
transaction.AddNewlyCreatedDBObject(record, true);
}
// The "V" property and allowed values are the confirmed legacy PXP-OA
// dynamic-block contract. Failure is pre-commit and leaves no entity.
private static void SetConfirmedVisibility(BlockReference annotation, string visibilityState)
{
var property = annotation.DynamicBlockReferencePropertyCollection
.Cast<DynamicBlockReferenceProperty>()
.SingleOrDefault(value => string.Equals(value.PropertyName, "V", StringComparison.Ordinal));
var allowed = property?.GetAllowedValues().OfType<string>().ToArray() ?? [];
if (property is null || !allowed.Contains(visibilityState, StringComparer.Ordinal))
throw new InvalidOperationException($"ANNOTATION_VISIBILITY_STATE_UNSUPPORTED:Allowed={string.Join('|', allowed)}");
property.Value = visibilityState;
}
}
@@ -1,9 +1,11 @@
using System.Security.Cryptography;
using System.Collections.Specialized;
using System.Text;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.ProcessPower.DataLinks;
using Autodesk.ProcessPower.Drawings2d;
using Autodesk.ProcessPower.PnP3dObjects;
using Autodesk.ProcessPower.P3dProjectParts;
using Autodesk.ProcessPower.PlantInstance;
using Autodesk.ProcessPower.ProjectManager;
@@ -14,9 +16,11 @@ namespace PRecAnnot.Plant3D2026;
internal sealed class PlantRuntimeContextReader : IPlantRuntimeContextReader
{
public PlantRuntimeContext ReadCurrentContext()
public PlantRuntimeContext ReadCurrentContext() => ReadCurrentContext(null);
public PlantRuntimeContext ReadCurrentContext(Document? explicitDocument = null)
{
var document = AcadApplication.DocumentManager.MdiActiveDocument;
var document = explicitDocument ?? AcadApplication.DocumentManager.MdiActiveDocument;
if (document is null)
{
return Empty(false, false, "NO_ACTIVE_DOCUMENT", "Активный документ отсутствует.");
@@ -279,6 +283,8 @@ internal sealed class PlantObjectReader : IPlantObjectReader
return Array.Empty<PlantObjectSnapshot>();
}
var membership = PlantViewportMembershipReader.Read(viewport, dataLinks, rowIds);
foreach (var rowId in rowIds)
{
try
@@ -297,9 +303,11 @@ internal sealed class PlantObjectReader : IPlantObjectReader
LineNumber = Value("Line Number", "LineNumber", "LINENUMBER"),
SupportNumber = Value("Support Number", "SupportNumber"),
EquipmentNumber = Value("Equipment Number", "EquipmentNumber"),
Geometry = new GeometrySnapshot(),
Geometry = membership.GeometryFor(rowId),
Properties = properties.ToDictionary(x => x.Key, x => (string?)x.Value, StringComparer.OrdinalIgnoreCase),
Diagnostics = new[] { "GEOMETRY_NOT_MAPPED" }
Diagnostics = new[] { "GEOMETRY_NOT_MAPPED" },
MembershipStatus = membership.StatusFor(rowId),
MembershipEvidenceCodes = membership.EvidenceFor(rowId)
});
}
catch (Exception exception)
@@ -381,11 +389,147 @@ internal sealed class PlantObjectReader : IPlantObjectReader
}
}
internal sealed class PlantViewportMembershipReader
{
internal readonly record struct MembershipReadResult(
IReadOnlySet<int> SelectedRows,
ViewMembershipStatus DefaultStatus,
string DefaultCode,
IReadOnlyDictionary<int, GeometrySnapshot>? Geometry = null)
{
public ViewMembershipStatus StatusFor(int rowId) =>
DefaultStatus == ViewMembershipStatus.Resolved && !SelectedRows.Contains(rowId)
? ViewMembershipStatus.ObjectOutsideView
: DefaultStatus;
public IReadOnlyList<string> EvidenceFor(int rowId) =>
DefaultStatus == ViewMembershipStatus.Resolved && !SelectedRows.Contains(rowId)
? new[] { "VIEW_MEMBERSHIP_OBJECT_OUTSIDE_CLIP" }
: new[] { DefaultCode };
public GeometrySnapshot GeometryFor(int rowId) =>
Geometry is not null && Geometry.TryGetValue(rowId, out var geometry)
? geometry
: new GeometrySnapshot();
}
internal static MembershipReadResult Read(OrthoViewportInfo viewport, DataLinksManager dataLinks, Autodesk.ProcessPower.DataObjects.PnPRowIdArray sourceRows)
{
try
{
var project = PlantApplication.ProjectPart("Ortho") as OrthoProject;
if (project is null) return Unsupported("VIEW_MEMBERSHIP_API_UNSUPPORTED");
var candidates = project.DwgSheetDefinitions.AsArray()
.SelectMany(sheet => sheet.DwgViewDefs.AsArray()
.Where(view => string.Equals(StableId(sheet.DwgGuid.ToString(), view.ViewName), viewport.Id, StringComparison.Ordinal))
.Select(view => (sheet, view)))
.ToArray();
if (candidates.Length != 1) return Unsupported("VIEW_MEMBERSHIP_API_UNSUPPORTED");
var view = candidates[0].view;
if (!OrthoModelFilesContract.IsSupported(view, out var diagnostic))
{
RuntimeDiagnostics.Write("ViewportMembershipContractRejected", "WARNING", resultStatus: diagnostic.Code, details: diagnostic.Details);
return Unsupported("VIEW_MEMBERSHIP_API_UNSUPPORTED");
}
var selectedRows = new HashSet<int>();
var geometry = new Dictionary<int, GeometrySnapshot>();
var selected = view.DwgQuery.Execute(new StringCollection(), new StringCollection());
for (var index = 0; index < selected.Count; index++)
{
Autodesk.ProcessPower.Drawings2d.MdObjectId sourceId = default;
if (!selected.GetItem(index, ref sourceId) || sourceId.IsNull || sourceId.PrjObjectId.IsNull) continue;
try
{
var rowId = dataLinks.FindAcPpRowId(sourceId.PrjObjectId);
if (sourceRows.Contains(rowId))
{
selectedRows.Add(rowId);
if (TryReadPipePlacement(sourceId, dataLinks, out var placement)) geometry[rowId] = placement;
}
}
catch (DLException)
{
// A generated entity without a Piping DataLinks row is outside this reader's source set.
}
}
RuntimeDiagnostics.Write("ViewportMembershipReadCompleted", "INFO", resultStatus: "VIEW_MEMBERSHIP_FROM_CONFIRMED_PNP_ID", details: new Dictionary<string, object?>
{
["SelectedSourceRows"] = selectedRows.Count,
["SourceRows"] = sourceRows.Count
});
return new MembershipReadResult(selectedRows, ViewMembershipStatus.Resolved, "VIEW_MEMBERSHIP_FROM_CONFIRMED_PNP_ID", geometry);
}
catch (Exception exception)
{
RuntimeDiagnostics.Write("ViewportMembershipReadCompleted", "WARNING", resultStatus: "VIEW_MEMBERSHIP_API_UNSUPPORTED", exception: exception);
return Unsupported("VIEW_MEMBERSHIP_API_UNSUPPORTED");
}
}
private static MembershipReadResult Unsupported(string code) => new(new HashSet<int>(), ViewMembershipStatus.NotResolved, code);
// Confirmed Plant 3D 2026 linkage: DwgQuery.Execute returns MdObjectId,
// whose PrjObjectId identifies the source entity and whose Xform projects
// that source entity into the particular Ortho view. This is not a sheet
// coordinate heuristic.
private static bool TryReadPipePlacement(MdObjectId sourceId, DataLinksManager dataLinks, out GeometrySnapshot geometry)
{
geometry = new GeometrySnapshot();
var stage = "GetDatabase";
try
{
var openedByQuery = false;
// The source model is normally not an active document in an Ortho /b session.
// Ask MdObjectId to open its source database read-only and dispose it below when it did.
var sourceDatabase = sourceId.GetDatabase(true, ref openedByQuery);
try
{
stage = "StartTransaction";
using var transaction = sourceDatabase.TransactionManager.StartOpenCloseTransaction();
stage = "MakeAcDbObjectId";
var objectId = dataLinks.MakeAcDbObjectId(sourceId.PrjObjectId, sourceDatabase);
stage = "OpenPipe";
if (transaction.GetObject(objectId, OpenMode.ForRead) is not Pipe pipe) return false;
stage = "TransformStartPoint";
var point = pipe.StartPoint.TransformBy(sourceId.Xform);
geometry = new GeometrySnapshot
{
PaperPosition = new Point2Dto(point.X, point.Y),
Rotation = 0
};
return true;
}
finally
{
if (openedByQuery) sourceDatabase.Dispose();
}
}
catch (Exception exception)
{
RuntimeDiagnostics.Write("OrthoPipePlacementReadFailed", "ERROR", resultStatus: "ORTHO_PIPE_PLACEMENT_UNAVAILABLE", exception: exception, details: new Dictionary<string, object?> { ["PlacementStage"] = stage });
return false;
}
}
// Ortho viewport IDs are generated by PlantOrthoViewportReader from exactly this pair.
// Only an exact regenerated ID is accepted; the reader never chooses an arbitrary view.
private static string StableId(string sheetId, string? viewName)
{
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(sheetId + "|" + (viewName ?? string.Empty)));
return "vp-" + Convert.ToHexString(hash)[..20];
}
}
internal sealed class RuntimeWriteGuard(PRecAnnotExecutionMode mode) : IRuntimeWriteGuard
{
public void EnsureWriteAllowed(string operation)
{
if (mode == PRecAnnotExecutionMode.Apply) throw new InvalidOperationException("APPLY_MODE_NOT_IMPLEMENTED");
if (mode == PRecAnnotExecutionMode.Apply) return;
throw new InvalidOperationException("SMOKE_WRITE_BLOCKED");
}
}
@@ -0,0 +1,272 @@
using System.Text.Json;
using System.Reflection;
using System.Security.Cryptography;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.ProcessPower.PlantInstance;
using Autodesk.ProcessPower.ProjectManager;
using PRecAnnot.Abstractions;
using AcadApplication = Autodesk.AutoCAD.ApplicationServices.Application;
namespace PRecAnnot.Plant3D2026;
// Test-only observer. The AutoCAD /b processor, never this type, drives PXP-OA.
internal static class ScriptDrivenFirstApplyAutomation
{
private sealed record Config(string Phase, string OrthoDrawingPath, string EvidencePath, string FixtureMarkerPath, string ExpectedEntryAssemblySha256, bool Diagnostic);
private sealed record Run(int Sequence, string? PlanId, IReadOnlyList<string> Candidates, string? View, int Create, int Skip, bool? PermitAccepted, string? ApplyOutcome, int? DbmodBefore, int? DbmodAfter, AutomationEntityCounts? Before, AutomationEntityCounts? After, bool WillStart, bool Ended, bool Failed, bool Cancelled, int? SourceRows, int? MembershipResolved);
private static Config? config;
private static Document? fixture;
private static Database? database;
private static AutomationEntityCounts? baseline;
private static int? dbmodBaseline;
private static readonly List<Run> runs = [];
private static readonly List<string> transitions = [];
private static readonly List<string> commandEvents = [];
private static readonly List<AutomationSentinelEvidence> sentinels = [];
private static readonly List<AutomationCommandCorrelationEvidence> correlations = [];
private static PxpOaDiscoveryEvidence? discovery;
// Plant's implicit current-document lookup can lag between script commands.
// PREPARE has already proved this immutable context belongs to the live fixture;
// retain it for the two immediately following public PXP-OA commands only.
private static PlantRuntimeContext? preparedContext;
private static string? selectedTargetSnapshotId;
private static bool prepared;
private static bool terminal;
private static string? applyExceptionText;
internal static void Prepare()
{
try
{
if (terminal) return;
config = ReadConfig();
RequireGate(config);
discovery = DiscoverPxpOa(config);
RequireDiscovery(discovery);
var active = AcadApplication.DocumentManager.MdiActiveDocument ?? throw new InvalidOperationException("AUTOMATION_ACTIVE_DOCUMENT_MISSING");
if (!SamePath(active.Name, config.OrthoDrawingPath)) throw new InvalidOperationException("AUTOMATION_UNEXPECTED_ACTIVE_FIXTURE");
fixture = active;
database = active.Database ?? throw new InvalidOperationException("AUTOMATION_FIXTURE_DATABASE_MISSING");
fixture.CommandWillStart += OnWillStart;
fixture.CommandEnded += OnEnded;
fixture.CommandFailed += OnFailed;
fixture.CommandCancelled += OnCancelled;
var projectXml = Path.Combine(Path.GetDirectoryName(config.FixtureMarkerPath)!, "Project.xml");
if (!File.Exists(projectXml)) throw new InvalidOperationException("AUTOMATION_PROJECT_XML_MISSING");
var project = PlantProject.LoadProject(projectXml, false, string.Empty, string.Empty);
PlantApplication.SetCurrentProject(project, true);
var context = new PlantRuntimeContextReader().ReadCurrentContext(fixture);
if (!context.HasPlantProject || !context.IsOrthoDrawing || context.Viewports.Count != 1)
throw new InvalidOperationException("AUTOMATION_ORTHO_CONTEXT_INVALID");
var candidates = new PlantObjectReader().ReadObjects(context.Viewports.Single(), CancellationToken.None)
.Where(x => string.Equals(x.ObjectClass, "Pipe", StringComparison.OrdinalIgnoreCase) && x.MembershipStatus == ViewMembershipStatus.Resolved && x.Geometry.PaperPosition is not null)
.Select(x => x.SnapshotId).OrderBy(x => x, StringComparer.Ordinal).ToArray();
if (candidates.Length == 0) throw new InvalidOperationException("AUTOMATION_NO_ELIGIBLE_CANDIDATE");
preparedContext = context;
selectedTargetSnapshotId = candidates[0];
dbmodBaseline = Dbmod();
baseline = Counts(database);
prepared = true;
transitions.Add("Prepared");
Write(false, null);
}
catch (System.Exception exception) { Fail("AUTOMATION_PREPARE_FAILED", exception); }
}
internal static void RecordSentinel(string stage)
{
Guard(() =>
{
if (!prepared || terminal || fixture is null || database is null) return;
sentinels.Add(new(stage, fixture.Name, database.Filename, DateTimeOffset.UtcNow));
transitions.Add("Sentinel:" + stage);
Write(false, null);
});
}
internal static void RecordPxpOa(string? planId, IReadOnlyList<string> candidates, string? view, int create, int skip, bool? permitAccepted, int sourceRows, int membershipResolved)
{
if (!prepared || terminal || runs.Count == 0) return;
var last = runs[^1];
if (!last.WillStart || last.PlanId is not null) return;
runs[^1] = last with { PlanId = planId, Candidates = candidates, View = view, Create = create, Skip = skip, PermitAccepted = permitAccepted, SourceRows = sourceRows, MembershipResolved = membershipResolved };
}
internal static void RecordApplyOutcome(PipeAnnotationWriteOutcome outcome)
{
if (!prepared || terminal || runs.Count == 0) return;
var last = runs[^1];
runs[^1] = outcome == PipeAnnotationWriteOutcome.AlreadyExists
? last with { ApplyOutcome = outcome.ToString(), Create = 0, Skip = 1 }
: last with { ApplyOutcome = outcome.ToString() };
}
internal static void RecordApplyFailure(System.Exception exception)
{
if (!prepared || terminal) return;
applyExceptionText = exception.ToString();
Write(false, "AUTOMATION_APPLY_COMMAND_FAILED");
}
internal static bool TryGetPreparedContext(Document? commandDocument, out PlantRuntimeContext context)
{
context = null!;
if (!prepared || terminal || fixture is null || preparedContext is null || commandDocument is null || config is null) return false;
if (!SamePath(commandDocument.Name, config.OrthoDrawingPath) || !SamePath(fixture.Name, config.OrthoDrawingPath)) return false;
if (commandDocument.Database is null || database is null || !SamePath(commandDocument.Database.Filename, database.Filename)) return false;
context = preparedContext;
return true;
}
internal static string? SelectedTargetSnapshotId => prepared && !terminal ? selectedTargetSnapshotId : null;
internal static void FinalizeAutomation()
{
try
{
if (!prepared) throw new InvalidOperationException("AUTOMATION_NOT_PREPARED");
if (runs.Count != 2) throw new InvalidOperationException("AUTOMATION_EXPECTS_EXACTLY_TWO_PXP_OA");
if (runs.Any(x => !x.WillStart || !x.Ended || x.Failed || x.Cancelled)) throw new InvalidOperationException("AUTOMATION_PXP_OA_NOT_ENDED");
if (runs.Any(x => string.IsNullOrWhiteSpace(x.PlanId))) throw new InvalidOperationException("PXP_OA_PLAN_RESULT_NOT_CAPTURED");
if (!string.Equals(runs[0].PlanId, runs[1].PlanId, StringComparison.Ordinal)) throw new InvalidOperationException("AUTOMATION_PLAN_IDS_DIFFER");
var apply = string.Equals(config!.Phase, "Apply", StringComparison.OrdinalIgnoreCase);
if ((!apply && runs.Any(x => x.Create != 1)) || (apply && (runs[0].Create != 1 || runs[1].Create != 0))) throw new InvalidOperationException("AUTOMATION_CREATE_COUNTS_INVALID");
if (runs[0].Candidates.Count == 0) throw new InvalidOperationException("AUTOMATION_NO_ELIGIBLE_CANDIDATE");
if (apply)
{
if (runs[0].PermitAccepted != true) throw new InvalidOperationException("AUTOMATION_APPLY_PERMIT_NOT_ACCEPTED");
if (!string.Equals(runs[0].ApplyOutcome, PipeAnnotationWriteOutcome.Created.ToString(), StringComparison.Ordinal)) throw new InvalidOperationException("AUTOMATION_APPLY_CREATE_NOT_CONFIRMED");
if (!string.Equals(runs[1].ApplyOutcome, PipeAnnotationWriteOutcome.AlreadyExists.ToString(), StringComparison.Ordinal)) throw new InvalidOperationException("AUTOMATION_APPLY_DUPLICATE_NOT_CONFIRMED");
var undoCounts = Counts(database!);
if (!Equals(undoCounts, baseline)) throw new InvalidOperationException("AUTOMATION_UNDO_DID_NOT_RESTORE_BASELINE");
}
else if (Dbmod() != dbmodBaseline || !Equals(Counts(database!), baseline)) throw new InvalidOperationException("AUTOMATION_INSPECTOR_INVARIANCE_FAILED");
terminal = true;
transitions.Add(apply ? "ApplyCompleted" : "PreviewCompleted");
Write(true, null);
Unsubscribe();
}
catch (System.Exception exception) { Fail("PREVIEW_ACCEPTANCE_FAILED", exception); }
}
private static void OnWillStart(object sender, CommandEventArgs args)
{
Guard(() =>
{
commandEvents.Add("WillStart:" + args.GlobalCommandName);
var correlation = Correlate("WillStart", sender, args.GlobalCommandName, runs.Count < 2);
correlations.Add(correlation);
if (correlation.RejectionReason is not null) return;
runs.Add(new Run(runs.Count + 1, null, [], null, 0, 0, null, null, Dbmod(), null, Counts(database!), null, true, false, false, false, null, null));
transitions.Add("PxpOaWillStart:" + runs.Count);
});
}
private static void OnEnded(object sender, CommandEventArgs args) => Finish(sender, args, false, false);
private static void OnFailed(object sender, CommandEventArgs args) => Finish(sender, args, true, false);
private static void OnCancelled(object sender, CommandEventArgs args) => Finish(sender, args, false, true);
private static void Finish(object sender, CommandEventArgs args, bool failed, bool cancelled)
{
Guard(() =>
{
var kind = failed ? "Failed" : cancelled ? "Cancelled" : "Ended";
commandEvents.Add(kind + ":" + args.GlobalCommandName);
var correlation = Correlate(kind, sender, args.GlobalCommandName, runs.Count > 0);
correlations.Add(correlation);
if (correlation.RejectionReason is not null) return;
var last = runs[^1];
if (last.Ended || last.Failed || last.Cancelled) return;
runs[^1] = last with { Ended = !failed && !cancelled, Failed = failed, Cancelled = cancelled, DbmodAfter = Dbmod(), After = Counts(database!) };
transitions.Add(failed ? "PxpOaFailed" : cancelled ? "PxpOaCancelled" : "PxpOaEnded:" + last.Sequence);
Write(false, null);
});
}
private static AutomationCommandCorrelationEvidence Correlate(string kind, object sender, string rawName, bool sequenceAccepted)
{
var normalized = rawName.Trim();
var command = string.Equals(normalized, "PXP-OA", StringComparison.OrdinalIgnoreCase);
var state = prepared && !terminal && config is not null && fixture is not null && database is not null;
var senderDocument = sender as Document;
var senderPath = senderDocument?.Name;
var expectedPath = config?.OrthoDrawingPath;
var activePath = AcadApplication.DocumentManager.MdiActiveDocument?.Name;
var fixturePath = senderPath is not null && expectedPath is not null && SamePath(senderPath, expectedPath);
var activeFixture = activePath is not null && expectedPath is not null && SamePath(activePath, expectedPath);
var document = fixturePath && activeFixture;
var databaseMatch = senderDocument?.Database is not null && database is not null && SamePath(senderDocument.Database.Filename, database.Filename);
var rejection = !command ? "COMMAND_NAME_MISMATCH" : !state ? "NOT_PREPARED" : !document ? "DOCUMENT_IDENTITY_MISMATCH" : !databaseMatch ? "DATABASE_IDENTITY_MISMATCH" : !sequenceAccepted ? "UNEXPECTED_SEQUENCE" : null;
return new(kind, rawName, normalized, command, state, document, databaseMatch, fixturePath, sequenceAccepted, rejection, sender.GetType().FullName, senderPath, expectedPath, activePath, runs.Count, DateTimeOffset.UtcNow);
}
private static void Guard(Action action) { try { if (!terminal) action(); } catch (System.Exception ex) { Fail("AUTOMATION_EVENT_HANDLER_FAILED", ex); } }
private static void Fail(string code, System.Exception exception) { if (terminal) return; terminal = true; transitions.Add("Failed:" + code); try { Write(false, code + ":" + exception); } finally { Unsubscribe(); } }
private static void Unsubscribe()
{
if (fixture is not null) try { fixture.CommandWillStart -= OnWillStart; fixture.CommandEnded -= OnEnded; fixture.CommandFailed -= OnFailed; fixture.CommandCancelled -= OnCancelled; } catch { }
preparedContext = null;
selectedTargetSnapshotId = null;
}
private static int? Dbmod() { try { return Convert.ToInt32(AcadApplication.GetSystemVariable("DBMOD")); } catch { return null; } }
private static AutomationEntityCounts Counts(Database db)
{
using var tx = db.TransactionManager.StartTransaction();
var space = (BlockTableRecord)tx.GetObject(db.CurrentSpaceId, OpenMode.ForRead);
var total = 0; var pipes = 0; var xdata = 0; var owned = 0;
foreach (ObjectId id in space) { if (tx.GetObject(id, OpenMode.ForRead) is not Entity entity) continue; total++; if (entity is BlockReference br && string.Equals(br.Name, "PIPE", StringComparison.OrdinalIgnoreCase)) pipes++; if (entity.XData is not null) { foreach (TypedValue value in entity.XData) if (value.Value is string text && string.Equals(text, "PXP-OA", StringComparison.OrdinalIgnoreCase)) { xdata++; owned++; break; } } }
return new(total, pipes, xdata, owned);
}
private static Config ReadConfig()
{
var path = Environment.GetEnvironmentVariable("P_REC_ANNOT_AUTOMATION_CONFIG") ?? throw new InvalidOperationException("AUTOMATION_CONFIG_MISSING");
using var json = JsonDocument.Parse(File.ReadAllText(path)); var root = json.RootElement;
return new(root.GetProperty("phase").GetString() ?? "Preview", root.GetProperty("orthoDrawingPath").GetString() ?? throw new InvalidOperationException("AUTOMATION_ORTHO_PATH_REQUIRED"), root.GetProperty("evidencePath").GetString() ?? throw new InvalidOperationException("AUTOMATION_EVIDENCE_PATH_REQUIRED"), root.GetProperty("fixtureMarkerPath").GetString() ?? throw new InvalidOperationException("AUTOMATION_MARKER_PATH_REQUIRED"), 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)
{
if (!string.Equals(Environment.GetEnvironmentVariable("P_REC_ANNOT_AUTOMATION"), "FirstLimitedApplyRuntimeTest", StringComparison.Ordinal) || !string.Equals(Environment.GetEnvironmentVariable("P_REC_ANNOT_LAUNCHER_MODE"), "NextPilot", StringComparison.Ordinal) || !File.Exists(value.FixtureMarkerPath)) throw new InvalidOperationException("AUTOMATION_GATE_REJECTED");
}
private static bool SamePath(string left, string right) => string.Equals(Path.GetFullPath(left), Path.GetFullPath(right), StringComparison.OrdinalIgnoreCase);
private static void Write(bool completed, string? error)
{
if (config is null) return;
var first = runs.ElementAtOrDefault(0); var second = runs.ElementAtOrDefault(1);
var apply = string.Equals(config.Phase, "Apply", StringComparison.OrdinalIgnoreCase);
var afterUndo = apply && terminal ? Counts(database!) : null;
var evidence = new FirstApplyAutomationEvidence { Phase = config.Phase, Completed = completed, Prepared = prepared, FunctionalEvidenceCompleted = completed, ErrorCode = error, DbmodBefore = dbmodBaseline, DbmodAfterCommand1 = first?.DbmodAfter, DbmodAfterCommand2 = second?.DbmodAfter, DbmodAfterUndo = apply && terminal ? Dbmod() : null, Baseline = baseline, AfterCommand1 = first?.After, AfterCommand2 = second?.After, AfterUndo = afterUndo, CompletedCommands = runs.Where(x => x.Ended).Select(x => "PXP-OA").Concat(apply && commandEvents.Any(x => string.Equals(x, "Ended:UNDO", StringComparison.OrdinalIgnoreCase)) ? ["UNDO"] : []).ToArray(), PlanIdRun1 = first?.PlanId, PlanIdRun2 = second?.PlanId, PlanIdsEqual = first is not null && second is not null ? string.Equals(first.PlanId, second.PlanId, StringComparison.Ordinal) : null, EligibleCandidateCount = first?.Candidates.Count, SelectedCandidateHash = first?.Candidates.OrderBy(x => x, StringComparer.Ordinal).FirstOrDefault(), ViewHash = first?.View, CreateRun1 = first?.Create, CreateRun2 = second?.Create, SkipRun2 = second?.Skip, ApplyOutcomeRun1 = first?.ApplyOutcome, ApplyOutcomeRun2 = second?.ApplyOutcome, PermitAccepted = first?.PermitAccepted, CommandsCompleted = runs.Count == 2 && runs.All(x => x.Ended) && (!apply || commandEvents.Any(x => string.Equals(x, "Ended:UNDO", StringComparison.OrdinalIgnoreCase))), InspectorsCompleted = baseline is not null && first?.After is not null && second?.After is not null && (!apply || afterUndo is not null), EvidenceFlushed = true, ExceptionText = applyExceptionText, CurrentState = terminal ? (completed ? "Completed" : "Failed") : "Prepared", StateTransitions = transitions, PxpOaDiscovery = discovery, Sentinels = sentinels, CommandEvents = commandEvents, CommandCorrelations = correlations };
Directory.CreateDirectory(Path.GetDirectoryName(config.EvidencePath)!);
var temp = config.EvidencePath + ".tmp"; File.WriteAllText(temp, JsonSerializer.Serialize(evidence)); File.Move(temp, config.EvidencePath, true);
}
private static PxpOaDiscoveryEvidence DiscoverPxpOa(Config value)
{
var entry = typeof(EntryPoint).Assembly;
var loaded = AppDomain.CurrentDomain.GetAssemblies().Any(x => ReferenceEquals(x, entry));
var location = entry.Location;
var hash = File.Exists(location) ? Convert.ToHexString(SHA256.HashData(File.ReadAllBytes(location))) : null;
var declarations = AppDomain.CurrentDomain.GetAssemblies().SelectMany(Declarations).Where(x => string.Equals(x.Name, "PXP-OA", StringComparison.OrdinalIgnoreCase)).ToArray();
var owner = declarations.SingleOrDefault(x => x.Assembly == entry);
var commandClass = entry.GetCustomAttributesData().Any(x => string.Equals(x.AttributeType.FullName, "Autodesk.AutoCAD.Runtime.CommandClassAttribute", StringComparison.Ordinal) && x.ConstructorArguments.Any(a => string.Equals(a.Value as Type, typeof(EntryPoint))));
return new(owner is not null, owner?.Name, owner?.Group, owner?.Flags, owner?.Type.FullName, owner?.Assembly.GetName().Name, location, hash, loaded, commandClass, hash is not null && string.Equals(hash, value.ExpectedEntryAssemblySha256, StringComparison.OrdinalIgnoreCase), declarations.Length);
}
private sealed record Declaration(string Name, string? Group, string? Flags, Type Type, Assembly Assembly);
private static IEnumerable<Declaration> Declarations(Assembly assembly)
{
Type[] types; try { types = assembly.GetTypes(); } catch (ReflectionTypeLoadException exception) { types = exception.Types.Where(x => x is not null).Cast<Type>().ToArray(); }
foreach (var type in types) foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)) foreach (var attribute in method.GetCustomAttributesData().Where(x => string.Equals(x.AttributeType.FullName, "Autodesk.AutoCAD.Runtime.CommandMethodAttribute", StringComparison.Ordinal)))
{
var name = attribute.ConstructorArguments.FirstOrDefault().Value as string;
if (string.IsNullOrWhiteSpace(name)) continue;
var group = attribute.NamedArguments.FirstOrDefault(x => x.MemberName == "GroupName").TypedValue.Value as string;
var flags = attribute.NamedArguments.FirstOrDefault(x => x.MemberName == "Flags").TypedValue.Value?.ToString();
yield return new(name, group, flags, type, assembly);
}
}
private static void RequireDiscovery(PxpOaDiscoveryEvidence value)
{
if (!value.AssemblyLoaded) throw new InvalidOperationException("PXP_OA_ASSEMBLY_NOT_LOADED");
if (!value.BundleHashMatched) throw new InvalidOperationException("PXP_OA_BUNDLE_HASH_MISMATCH");
if (!value.CommandClassIncluded) throw new InvalidOperationException("PXP_OA_COMMANDCLASS_NOT_REGISTERED");
if (value.DeclarationCount > 1) throw new InvalidOperationException("PXP_OA_COMMAND_COLLISION");
if (value.DeclarationCount == 0 || !value.Found) throw new InvalidOperationException("PXP_OA_COMMAND_NOT_FOUND");
if (!string.Equals(value.GlobalName, "PXP-OA", StringComparison.Ordinal)) throw new InvalidOperationException("PXP_OA_GLOBAL_NAME_MISMATCH");
}
}
@@ -61,6 +61,170 @@ public sealed class FoundationTests
Assert.Contains("[CommandMethod(\"PHP_REC_ANNOT\")]", source);
Assert.Contains("[CommandMethod(\"PXP-AAV\")]", source);
Assert.Contains("[CommandMethod(\"PXP-OA\")]", source);
Assert.Contains("PHP_REC_AUTOMATION_READY", source);
}
[Fact] public void AutomationBootstrap_DefersPlantAndDocumentAccessUntilCoordinator()
{
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs"));
var bootstrap = source[..source.IndexOf("internal sealed record FirstApplyAutomationEvidence", StringComparison.Ordinal)];
Assert.DoesNotContain("PlantProject.LoadProject", bootstrap);
Assert.DoesNotContain("MdiActiveDocument", bootstrap);
Assert.Contains("AcadApplication.Idle += OnIdle", bootstrap);
Assert.Contains("observedIdle = true", bootstrap);
Assert.Contains("coordinatorStartCount++", bootstrap);
}
[Fact] public void AutomationLifecycleProbe_CompletesBeforePlantActivation()
{
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs"));
Assert.Contains("LifecycleProbe", source);
Assert.True(source.IndexOf("CompleteLifecycleProbe", StringComparison.Ordinal) < source.IndexOf("PlantProject.LoadProject", StringComparison.Ordinal));
Assert.Contains("PostStartupReady", source);
}
[Fact] public void AutomationProjectActivation_WaitsForFixtureDocumentAndQuiescence()
{
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs"));
var activation = source[source.IndexOf("private void TryActivateProjectAndVerifyContext", StringComparison.Ordinal)..];
Assert.Contains("!fixtureDocumentActivated", activation);
Assert.Contains("!AcadApplication.IsQuiescent", activation);
Assert.True(activation.IndexOf("PlantProject.LoadProject", StringComparison.Ordinal) > activation.IndexOf("AutomationBeforeProjectActivation", StringComparison.Ordinal));
}
[Fact] public void AutomationDocumentActivation_IsSubscribedBeforeFixtureOpen()
{
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs"));
Assert.True(source.IndexOf("DocumentActivated += OnDocumentActivated", StringComparison.Ordinal) < source.IndexOf("DocumentManager.Open(config.OrthoDrawingPath", StringComparison.Ordinal));
Assert.Contains("FixtureDocumentActivated", source);
}
[Fact] public void AutomationDocumentActivation_RecoversSynchronousOpenEvent()
{
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs"));
var start = source[source.IndexOf("public void Start()", StringComparison.Ordinal)..source.IndexOf("private void CompleteLifecycleProbe", StringComparison.Ordinal)];
Assert.Contains("FixtureDocumentActiveAfterOpen", start);
Assert.Contains("fixtureDocumentActivated = true", start);
Assert.Contains("QueueReadinessBarrier();", start);
Assert.Contains("ReadinessBarrierCompleted", source);
Assert.Contains("TryActivateProjectAndVerifyContext();", source);
}
[Fact] public void QueueReadinessBarrier_RequiresActiveFixtureAndUsesSafeDispatch()
{
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs"));
var dispatcher = source[source.IndexOf("private async Task DispatchCommandAsync", StringComparison.Ordinal)..source.IndexOf("private void OnCommandEnded", StringComparison.Ordinal)];
Assert.Contains("ExecuteInCommandContextAsync", dispatcher);
Assert.Contains("MdiActiveDocument", dispatcher);
Assert.Contains("AUTOMATION_COMMAND_DOCUMENT_NOT_ACTIVE", dispatcher);
Assert.Contains("Editor.CommandAsync", dispatcher);
Assert.DoesNotContain("SendStringToExecute", dispatcher);
}
[Fact] public void AutomationControlledExit_QueuesNativeQuitAfterPublicCommand()
{
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs"));
Assert.Contains("RequestControlledExit", source);
Assert.Contains("AcadApplication.Idle += OnExitIdle", source);
Assert.Contains("QuitDispatchAttempted", source);
Assert.Contains("exitDocument.SendStringToExecute(\"_.QUIT _N\\n\", true, true, false);", source);
Assert.Contains("var exitDocument = AcadApplication.DocumentManager.MdiActiveDocument;", source);
}
[Fact] public void Callback_AfterDocumentDestroyed_DoesNotAccessDocument()
{
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs"));
Assert.Contains("if (document is null || e.Document is null) return;", source);
Assert.Contains("if (IsTerminal || document is null) return;", source);
}
[Fact] public void Idle_WhenActiveDocumentNull_DoesNotThrow()
{
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs"));
Assert.Contains("AUTOMATION_EXIT_DOCUMENT_UNAVAILABLE", source);
Assert.Contains("QuitDispatchSkippedNoLiveDocument", source);
}
[Fact] public void TerminalState_IgnoresFurtherCallbacks()
{
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs"));
Assert.Contains("private bool IsTerminal", source);
Assert.Contains("if (IsTerminal && callback != \"ControlledExitIdle\") return;", source);
}
[Fact] public void HandlerException_ProducesFailedEvidence()
{
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs"));
Assert.Contains("exceptionText = exception.ToString();", source);
Assert.Contains("ExceptionText = exceptionText", source);
Assert.Contains("FailFromCallback", source);
}
[Fact] public void HandlerException_DoesNotEscapeToAutoCad()
{
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs"));
Assert.Contains("private void RunCallback", source);
Assert.Contains("catch (System.Exception exception) { FailFromCallback", source);
Assert.Contains("=> RunCallback(\"Idle\"", source);
Assert.Contains("=> RunCallback(\"CommandFailed\"", source);
}
[Fact] public void Unsubscribe_IsIdempotent()
{
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs"));
Assert.Contains("private bool documentHandlersSubscribed", source);
Assert.Contains("private bool lifecycleHandlersSubscribed", source);
Assert.Contains("private void UnsubscribeHandlers()", source);
}
[Fact] public void ControlledExit_DoesNotReuseDestroyedDocument()
{
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs"));
var controlledExit = source[source.IndexOf("private void RequestControlledExit", StringComparison.Ordinal)..];
Assert.DoesNotContain("document.CloseAndDiscard", controlledExit);
Assert.Contains("var exitDocument = AcadApplication.DocumentManager.MdiActiveDocument;", controlledExit);
Assert.Contains("document = null;", controlledExit);
}
[Fact] public void FinalCommandCompletion_UsesItsQuiescentExitBoundary()
{
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs"));
Assert.Contains("Complete(true, null, safeToExitNow: true)", source);
Assert.Contains("RequestControlledExit(safeToExitNow);", source);
}
[Fact] public void TerminalEvidence_IsFlushedBeforeExitAttempt()
{
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs"));
var exit = source[source.IndexOf("private void DispatchNativeQuit", StringComparison.Ordinal)..];
Assert.True(exit.IndexOf("TryWriteEvidence(functionalCompleted, null);", StringComparison.Ordinal) < exit.IndexOf("SendStringToExecute(\"_.QUIT _N\\n", StringComparison.Ordinal));
Assert.Contains("EvidenceFlushed = evidenceFlushed", source);
Assert.Contains("CommandsCompleted", source);
Assert.Contains("InspectorsCompleted", source);
}
[Fact] public void ExitFailure_DoesNotInvalidateCompletedFunctionalEvidence()
{
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs"));
Assert.Contains("if (functionalCompleted)", source);
Assert.Contains("ExitFailureCode = exitFailureCode", source);
Assert.Contains("TryWriteEvidence(functionalCompleted, null);", source);
}
[Fact] public void ExitDispatch_DoesNotUseUnavailableDocument()
{
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs"));
var exit = source[source.IndexOf("private void DispatchNativeQuit", StringComparison.Ordinal)..];
Assert.Contains("AcadApplication.IsQuiescent", exit);
Assert.Contains("QuitDispatchSkippedNoLiveDocument", exit);
Assert.DoesNotContain("document ?? AcadApplication.DocumentManager", exit);
}
[Fact] public void ProductionRuntime_DoesNotEnableForceAfterEvidence()
{
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs"));
Assert.DoesNotContain("ForceAfterEvidence", source);
Assert.Contains("P_REC_ANNOT_AUTOMATION", source);
}
[Fact] public void PrimaryCommand_IsRegistered() => Assert.Contains("[CommandMethod(\"PHP_REC_ANNOT\")]", File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "EntryPoint.cs")));
@@ -135,6 +299,31 @@ public sealed class FoundationTests
Assert.Contains("VIEW_MEMBERSHIP_NOT_RESOLVED", plan.SkipReasons);
}
[Fact] public void ViewportMembership_OutsideClip_IsSkippedWithStableReason()
{
var plan = Build(new PlantObjectSnapshot { SnapshotId = "a", ObjectClass = "Pipe", LineNumber = "L-1", Geometry = new GeometrySnapshot(), MembershipStatus = ViewMembershipStatus.ObjectOutsideView });
Assert.Equal(AnnotationOperationKind.Skip, Assert.Single(plan.Operations).Kind);
Assert.Contains("VIEW_MEMBERSHIP_OBJECT_OUTSIDE_CLIP", plan.SkipReasons);
}
[Fact] public void AnnotationPlan_ResolvedMembership_AffectsPlanId()
{
var resolved = Build(new PlantObjectSnapshot { SnapshotId = "a", ObjectClass = "Pipe", LineNumber = "L-1", Geometry = new GeometrySnapshot(), MembershipStatus = ViewMembershipStatus.Resolved });
var outside = Build(new PlantObjectSnapshot { SnapshotId = "a", ObjectClass = "Pipe", LineNumber = "L-1", Geometry = new GeometrySnapshot(), MembershipStatus = ViewMembershipStatus.ObjectOutsideView });
Assert.NotEqual(resolved.PlanId, outside.PlanId);
}
[Fact] public void AnnotationPlan_ReorderedMembership_HasSamePlanId()
{
var first = Build(
new PlantObjectSnapshot { SnapshotId = "a", ObjectClass = "Pipe", LineNumber = "L-1", Geometry = new GeometrySnapshot(), MembershipStatus = ViewMembershipStatus.Resolved },
new PlantObjectSnapshot { SnapshotId = "b", ObjectClass = "Pipe", LineNumber = "L-2", Geometry = new GeometrySnapshot(), MembershipStatus = ViewMembershipStatus.ObjectOutsideView });
var second = Build(
new PlantObjectSnapshot { SnapshotId = "b", ObjectClass = "Pipe", LineNumber = "L-2", Geometry = new GeometrySnapshot(), MembershipStatus = ViewMembershipStatus.ObjectOutsideView },
new PlantObjectSnapshot { SnapshotId = "a", ObjectClass = "Pipe", LineNumber = "L-1", Geometry = new GeometrySnapshot(), MembershipStatus = ViewMembershipStatus.Resolved });
Assert.Equal(first.PlanId, second.PlanId);
}
[Fact] public void AnnotationPlan_MissingLineNumber_IsSkipped()
{
var plan = Build(Snapshot("a", "Pipe", null, null));
@@ -233,6 +422,22 @@ public sealed class FoundationTests
Assert.Contains("SOURCE_DATALINKS_NO_ROWS", source);
}
[Fact] public void ViewportMembership_UsesConfirmedPnpIdentityContract()
{
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "PlantReadPath.cs"));
Assert.Contains("view.DwgQuery.Execute", source);
Assert.Contains("sourceId.PrjObjectId", source);
Assert.Contains("dataLinks.FindAcPpRowId", source);
Assert.Contains("VIEW_MEMBERSHIP_FROM_CONFIRMED_PNP_ID", source);
}
[Fact] public void ViewportMembership_DoesNotSelectFirstViewport()
{
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "PlantReadPath.cs"));
Assert.DoesNotContain("DwgViewDefs.AsArray().First", source);
Assert.Contains("candidates.Length != 1", source);
}
[Fact] public void RuntimeInspector_IsDisabledByDefault()
{
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "RuntimeDiagnostics.cs"));
@@ -242,6 +447,68 @@ public sealed class FoundationTests
[Fact] public void ApplyMode_IsBlocked() => Assert.Throws<InvalidOperationException>(() => new ExecutionModeWriteGuard(PRecAnnotExecutionMode.Apply).EnsureWriteAllowed("test"));
[Fact] public void FirstLimitedApply_RequiresApplyMode()
{
var context = FirstApplyContext();
Assert.Throws<InvalidOperationException>(() => new FirstLimitedApplyPlanner().Prepare(PRecAnnotExecutionMode.DryRun, context.Plan, context.Viewport, context.Objects, context.Permit));
}
[Fact] public void FirstLimitedApply_RequiresFreshPlan()
{
var context = FirstApplyContext();
var permit = context.Permit with { ExpectedPlanId = "stale" };
Assert.Throws<InvalidOperationException>(() => new FirstLimitedApplyPlanner().Prepare(PRecAnnotExecutionMode.Apply, context.Plan, context.Viewport, context.Objects, permit));
}
[Fact] public void FirstLimitedApply_RequiresResolvedMembership()
{
var context = FirstApplyContext();
var objects = new[] { context.Objects[0] with { MembershipStatus = ViewMembershipStatus.NotResolved } };
Assert.Throws<InvalidOperationException>(() => new FirstLimitedApplyPlanner().Prepare(PRecAnnotExecutionMode.Apply, context.Plan, context.Viewport, objects, context.Permit));
}
[Fact] public void FirstLimitedApply_RequiresExactlyOneTarget()
{
var context = FirstApplyContext();
var permit = context.Permit with { TargetSnapshotId = "missing" };
Assert.Throws<InvalidOperationException>(() => new FirstLimitedApplyPlanner().Prepare(PRecAnnotExecutionMode.Apply, context.Plan, context.Viewport, context.Objects, permit));
}
[Fact] public void FirstLimitedApply_CreatesDeterministicOperation()
{
var context = FirstApplyContext();
var planner = new FirstLimitedApplyPlanner();
var first = planner.Prepare(PRecAnnotExecutionMode.Apply, context.Plan, context.Viewport, context.Objects, context.Permit);
var second = planner.Prepare(PRecAnnotExecutionMode.Apply, context.Plan, context.Viewport, context.Objects, context.Permit);
Assert.Equal(first.Operation.OperationId, second.Operation.OperationId);
Assert.Equal(first.Operation.DuplicateKey, second.Operation.DuplicateKey);
Assert.DoesNotContain("C:\\", first.Operation.DuplicateKey, StringComparison.OrdinalIgnoreCase);
}
[Fact] public void FirstLimitedApplyPlan_OneResolvedPipeCreatesOnePipeOperation()
{
var viewport = new OrthoViewportInfo { Id = "vp", DisplayName = "view", SourceDrawingName = "model.dwg" };
var plan = new AnnotationPlanBuilder().Build(new AnnotationPlanningRequest
{
CommandName = "PXP-OA", Viewport = viewport,
Objects = new[] { Snapshot("pipe", "Pipe", null), Snapshot("other", "Valve", "TEST") },
LimitedApplyTargetSnapshotId = "pipe", LimitedApplyRuleId = FirstLimitedApplyPlanner.RuleId
});
Assert.Equal(1, plan.Summary.CreateCount);
Assert.Equal("PIPE", Assert.Single(plan.Operations.Where(x => x.Kind == AnnotationOperationKind.Create)).AnnotationType);
Assert.Contains("APPLY_NOT_SELECTED_TARGET", plan.Operations.Single(x => x.SourceSnapshotId == "other").Reasons);
}
[Fact] public void FirstLimitedApplyPlan_SelectionChangesCanonicalPlanId()
{
var viewport = new OrthoViewportInfo { Id = "vp", DisplayName = "view", SourceDrawingName = "model.dwg" };
var objects = new[] { Snapshot("one", "Pipe", null), Snapshot("two", "Pipe", null) };
var builder = new AnnotationPlanBuilder();
var one = builder.Build(new AnnotationPlanningRequest { CommandName = "PXP-OA", Viewport = viewport, Objects = objects, LimitedApplyTargetSnapshotId = "one", LimitedApplyRuleId = FirstLimitedApplyPlanner.RuleId });
var two = builder.Build(new AnnotationPlanningRequest { CommandName = "PXP-OA", Viewport = viewport, Objects = objects, LimitedApplyTargetSnapshotId = "two", LimitedApplyRuleId = FirstLimitedApplyPlanner.RuleId });
Assert.NotEqual(one.PlanId, two.PlanId);
}
[Fact] public void DryRunPath_HasNoForWriteOperations()
{
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "PlantReadPath.cs"));
@@ -251,6 +518,21 @@ public sealed class FoundationTests
Assert.DoesNotContain("SetProperties", source);
}
private static (AnnotationPlan Plan, OrthoViewportInfo Viewport, PlantObjectSnapshot[] Objects, FirstLimitedApplyPermit Permit) FirstApplyContext()
{
var viewport = new OrthoViewportInfo { Id = "vp", DisplayName = "view", SourceDrawingName = "model.dwg" };
var objects = new[] { Snapshot("pipe", "Pipe", "TEST") };
var plan = new AnnotationPlanBuilder().Build(new AnnotationPlanningRequest
{
CommandName = "PXP-OA",
Viewport = viewport,
Objects = objects,
LimitedApplyTargetSnapshotId = "pipe",
LimitedApplyRuleId = FirstLimitedApplyPlanner.RuleId
});
return (plan, viewport, objects, new FirstLimitedApplyPermit { FixtureMarker = FirstLimitedApplyPlanner.FixtureMarker, AllowApply = true, TargetSnapshotId = "pipe", ExpectedPlanId = plan.PlanId });
}
private static AnnotationPlan Build(params PlantObjectSnapshot[] objects) => new AnnotationPlanBuilder().Build(new AnnotationPlanningRequest { CommandName = "PHP_REC_ANNOT", Viewport = new OrthoViewportInfo { Id = "vp", DisplayName = "A", SourceDrawingName = "test.dwg" }, Objects = objects });
private static PlantObjectSnapshot Snapshot(string id, string type, string? line, string? tag = "TAG") => new() { SnapshotId = id, ObjectClass = type, LineNumber = line, Tag = tag, Geometry = new GeometrySnapshot { PaperPosition = new Point2Dto(1, 2) }, MembershipStatus = ViewMembershipStatus.Resolved };
}
@@ -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); }
}
+7
View File
@@ -0,0 +1,7 @@
# Autodesk Launcher architecture
This section is the single architecture record for the corporate Autodesk launcher, its legacy P_Rec_Annot loading path, and the parallel Launcher Next foundation. Statements are labelled `CONFIRMED`, `HIGH_CONFIDENCE`, `INFERRED`, or `UNKNOWN` and link to the audit evidence map.
The AS-IS flow is a locally synchronized legacy deployment with generated AutoLISP loading. Launcher Next is a .NET 8, one-visible-EXE design that stages signed-by-hash local packages and loads one managed entry assembly. It is a design/foundation only: production configuration, AutoCAD profiles, trusted paths, network roots and deploys were not changed.
See [AS-IS](as-is/system-context.md), [target](target/architecture.md), [legacy compatibility and pilot](target/legacy-compatibility.md), [ADRs](adr/ADR-001-parallel-rewrite.md), and [evidence map](evidence-map.csv).
@@ -0,0 +1,3 @@
# ADR-001: Parallel rewrite
**Decision:** build Launcher Next beside legacy. **Status:** accepted. **Why:** legacy installer performs destructive synchronization and runtime bootstrap remains partly unknown. **Consequence:** Legacy remains default and rollback is immediate.
@@ -0,0 +1,3 @@
# ADR-002: Single managed entrypoint
**Decision:** NETLOAD only `PRecAnnot.Plant3D2026.dll`. **Status:** accepted. **Why:** removes orchestration of 20 independent loads and centralizes commands/Ribbon. **Consequence:** internal dependencies must be packaged as CLR dependencies, not NETLOAD targets.
@@ -0,0 +1,3 @@
# ADR-003: Local versioned cache
**Decision:** activate immutable local versions through a pointer. **Status:** accepted. **Why:** avoids in-use DLL replacement and supports offline starts. **Consequence:** retain current/previous versions and clean staging separately.
@@ -0,0 +1,3 @@
# ADR-004: Atomic update
**Decision:** stage, validate, rename then switch pointer. **Status:** accepted. **Why:** prevents partial activation. **Consequence:** locked/cancelled updates retain current version and report diagnostics.
@@ -0,0 +1,3 @@
# ADR-005: Legacy fallback
**Decision:** mode flag defaults to Legacy; NextPilot requires explicit selection. **Status:** accepted. **Why:** startup evidence is incomplete and production configuration must not be changed implicitly. **Consequence:** launcher must not start AutoCAD twice after a Next failure.
@@ -0,0 +1,3 @@
# ADR-006: No direct UNC assembly loading
**Decision:** validate and load only local package copies. **Status:** accepted. **Why:** network availability and mid-session replacement are unreliable; direct UNC loading is not proven as a required legacy contract. **Consequence:** a local cache is mandatory for Next packages.
@@ -0,0 +1,10 @@
# AS-IS component model
`All_Serv_InstallUpdater` updates and launches `All_Serv_Install`; `All_Serv_Install` is the confirmed visible installer/synchronizer. `All_Serv_Load` is an independent historical/alternate executable. `P_Serv_Starter` is a separate bootstrap repository selected through installer configuration, not a project reference.
```text
Updater (optional) -> Visible installer -> local Applications cache -> P_Serv_Starter
P_Serv_Starter -> generated LISP + local batch -> Plant 3D -> OrthoLoader -> module settings -> 20 DLL
```
Network access is established for installation/update. Legacy module loading is intended from local Applications, not proven to be direct UNC loading. The installed state was absent on the audited workstation.
@@ -0,0 +1,11 @@
# AS-IS configuration contracts
| Contract | Format / owner | Consumer | Validation / fallback | Versioning / machine behavior | Confidence |
|---|---|---|---|---|---|
| Installer Settings.xml | XML, All_Serv_Install | installer and P_Serv_Starter | parse errors stop bootstrap; detailed fallback unknown | hard-coded server/local paths and machine rules | CONFIRMED |
| Plugin catalog/install | XML fields | generated LISP | only existing local catalog emits load | order follows configuration | CONFIRMED |
| OrthoLoader Settings.xml | XML, P_Rec deployment | OrthoLoader | per-module behavior partially known | ordered 20 module list | CONFIRMED |
| AutoCAD executable/version | batch/source/config | local batch | exact current version unknown | machine-specific | HIGH_CONFIDENCE |
| Profile/autoload | workstation AutoCAD config | AutoCAD | unknown | machine-specific | UNKNOWN |
| Ribbon resources | deployed module resources/network possible | legacy modules | absence behavior unknown | unknown | UNKNOWN |
| Offline mode | implicit local files | launcher/user | no proven policy | network dependent at sync | UNKNOWN |
@@ -0,0 +1,12 @@
# AS-IS failure modes
| Failure | Detection | Current handling | User-visible result | Data risk | Recovery | Target handling |
|---|---|---|---|---|---|---|
| Network unavailable | filesystem/XML operation | incomplete evidence | install/update may fail | stale files | unknown | use valid local cache |
| Partial copy / version skew | no proven transaction | copy/delete code | missing module | load failure | reinstall | staging + atomic pointer |
| Missing DLL / corrupted Settings.xml | generated load or loader parse | warnings/behavior varies | command/ribbon missing | none to DWG at load | manual repair | manifest validation |
| One of 20 modules incompatible | ._netload result | sequential continuation unknown | partial functionality | inconsistent UI | manual repair | one entry package compatibility check |
| Repeated NETLOAD / duplicate ribbon | runtime | module-specific | duplicate UI possible | UI only | restart | idempotent registry |
| No write permission / AV lock | file operations | errors/messages vary | launch blocked | partial install | manual repair | diagnostics and no activation |
| AutoCAD already running / profile missing / LISP/BAT absent | not fully proven | unknown | bootstrap may not execute | none | manual | explicit duplicate policy/preflight |
| Update during use | legacy copy model | unknown | locked or mixed files | runtime instability | manual | versioned immutable directories |
@@ -0,0 +1,13 @@
# AS-IS file flow
| File | Created/copied by | Source -> destination | Read/executed by | Generated | Required phase | Mutable | Failure impact | Target |
|---|---|---|---|---|---|---|---|---|
| P_Serv_Starter.exe | installer | configured local support location | installer | static | install | replaced by updater | no bootstrap | Next EXE workflow |
| start_plant_3d.bat | external local support | expected local location | P_Serv_Starter | static | startup | external | Plant not started | ProcessStartInfo |
| launcher_starter.lsp | P_Serv_Starter | bootstrap output -> Applications | AutoCAD (mechanism unknown) | copied | startup | overwritten | generated loader not activated | temporary Next script |
| _app_netload.lsp | P_Serv_Starter | generated in Applications | launcher_starter | generated | startup | overwritten | no plugin load | temporary Next script |
| app_netload.lsp | launcher_starter | renamed local generated file | AutoCAD | generated | startup | mutable | no selected plugin load | one NETLOAD script |
| OrthoLoader.dll | installer | network catalog -> local Applications/P_Rec_Annot | AutoCAD | static | startup | versioned only by legacy copy | P_Rec chain fails | PRecAnnot entry assembly |
| Settings.xml | legacy deployment | adjacent to loader | OrthoLoader | static | startup | network/local unknown | modules unavailable | versioned manifest |
| P_Rec DLL / icons / help / templates / project XML | legacy catalog | network -> local catalog | modules | static | command dependent | varies | feature-specific failure | explicit optional resources |
| AutoCAD profile references | workstation | external configuration | AutoCAD | static | startup | external | bootstrap may not run | explicit validated profile |
@@ -0,0 +1,17 @@
# AS-IS installation sequence
```mermaid
sequenceDiagram
participant U as User
participant I as All_Serv_Install
participant N as Corporate network
participant C as Local Applications
participant S as P_Serv_Starter
U->>I: select product/plugins
I->>N: read Settings.xml and selected files
I->>C: recursively copy catalogs
I->>S: start with product, target, settings, project, workdir
S->>C: create _app_netload.lsp and launcher_starter.lsp
```
Partial-copy recovery and all offline branches are not fully proven.
@@ -0,0 +1,8 @@
# Known unknowns
- `UNKNOWN_BOOTSTRAP`: automatic `launcher_starter.lsp` activation through `acaddoc.lsp`.
- Actual runtime files and local cache were absent on the audited workstation.
- Exact startup mechanism for every AutoCAD/Plant release and profile.
- Network-offline behavior across all installer/updater/module branches.
- Full per-module failure continuation, duplicate-load handling, resources and command-time network dependencies.
- Actual deployed updater shortcut and current Plant executable arguments.
@@ -0,0 +1,27 @@
# AS-IS performance profile
The startup path performs network-dependent synchronization when installer/update is used, writes generated LISP files, starts a batch and may load 20 assemblies sequentially. No timings were collected; claims about latency, parallelism, cache hit rate, or behavior under unavailable network are `UNKNOWN`.
| Source-level observation | Evidence | Performance implication | Confidence |
|---|---|---|---|
| Installer recursively copies selected catalogs and can delete files/directories | `All_Serv_Install/Form1.cs` copy path | filesystem and potentially network traversal; UI blocking behavior requires measurement | CONFIRMED |
| Updater computes MD5/control sums and replaces local files | `All_Serv_InstallUpdater/Form1.cs` | repeat hash/read/copy work; no measured count or time | CONFIRMED |
| Bootstrap emits one generated loader command per selected installed catalog | `P_Serv_Starter/Program.cs` | sequential startup work grows with selected modules | CONFIRMED |
| OrthoLoader sequentially `. _netload`s 20 configured modules | legacy loading audit | 20 managed loads before all functionality is ready | CONFIRMED |
| Repeated load on document/workspace events | no complete event evidence | cannot assert | UNKNOWN |
No measured count is claimed for network requests, full copies, UI waits, `Process.WaitForExit`, `Thread.Sleep`, or repeated document-level loading. Those are measurement subjects, not observed facts.
## Measurement plan (proposed)
| Scenario | Start | End | Proposed metrics |
|---|---|---|---|
| Cold start | launcher action | Ribbon ready | elapsed time, files hashed/copied, bytes, NETLOAD count |
| Warm start | launcher action | Ribbon ready | cache hit, network requests, elapsed time |
| Offline start | launcher action with network unavailable | Plant prepared/started | valid cache use, retry count, user status |
| No-update start | catalog read | launch prepared | manifest comparison and file operations |
| Update start | catalog read | activation complete | staging time, hash time, atomic switch time |
| Rollback | rollback request | previous pointer active | elapsed time and success/failure |
| Plugin readiness | Plant process created | Initialize and Ribbon ready | process-start, entry-load, command/Ribbon timestamps |
Targets are **PROPOSED** until baseline data is collected: one entry NETLOAD, zero network assembly loads, zero in-place replacement of active package files, and no repeated full copy on a valid no-update start.
@@ -0,0 +1,3 @@
# AS-IS security model
Legacy code writes local support files and uses generated LISP plus `NETLOAD`. It relies on the corporate local trusted support convention, but this audit did not change or verify `TRUSTEDPATHS`, profiles, registry demand loading, or deployment permissions. Network source access occurs during installation/update. The audit does not establish code signing, strong validation, complete hash coverage, or secret handling. Treat all of those as gaps.
@@ -0,0 +1,24 @@
# AS-IS startup sequence
```mermaid
sequenceDiagram
participant U as User
participant I as All_Serv_Install
participant S as P_Serv_Starter
participant B as start_plant_3d.bat
participant A as Plant 3D
participant L as launcher_starter.lsp
participant O as OrthoLoader.dll
U->>I: select Plant product/project
I->>S: start configured bootstrap
S->>S: create _app_netload.lsp; copy LISP
S->>B: Process.Start
B->>A: launch (details UNKNOWN)
Note over A,L: UNKNOWN_BOOTSTRAP: automatic LISP invocation not observed
L->>L: rename/load app_netload.lsp
L->>O: NETLOAD local OrthoLoader.dll
O->>O: read Settings.xml; ._netload ordered modules
O->>A: create PluginsOrtho ribbon
```
The lower sequence is conditional on `launcher_starter.lsp` being started; the initiating AutoCAD configuration is unknown.
@@ -0,0 +1,19 @@
# AS-IS system context
| Component | Owner / repository | Process / runtime | Inputs / outputs | Side effects | Replacement | Evidence / confidence |
|---|---|---|---|---|---|---|
| User | Engineering | desktop | selects product and project | starts launcher | retained | launcher audit / CONFIRMED |
| All_Serv_Install | `All_Serv_Install` | .NET Framework WinForms | server Settings.xml; local Applications; starts bootstrap | synchronizes and deletes local files | Launcher Next replaces gradually | launcher audit / CONFIRMED |
| P_Serv_Starter | `P_Serv_Starter` | .NET Framework EXE | settings + paths; generated LISP | writes loader files; starts batch | Next startup builder | source audit / CONFIRMED |
| start_plant_3d.bat | local support directory | cmd/batch | local support files | starts Plant process | direct ProcessStartInfo when viable | source reference only / HIGH_CONFIDENCE |
| launcher_starter.lsp | P_Serv_Starter content | AutoLISP | `_app_netload.lsp` | updates SupportPath; loads generated LISP | temporary pilot script | source audit / CONFIRMED |
| app_netload.lsp | generated locally | AutoLISP | plugin catalog/install entries | NETLOADs selected local modules | one entry NETLOAD | source audit / CONFIRMED |
| AutoCAD Plant 3D | Autodesk | acad.exe, Plant runtime | profile, drawing, local plugin | executes commands | retained | product discovery / CONFIRMED |
| OrthoLoader.dll | legacy P_Rec_Annot | managed AutoCAD extension | adjacent Settings.xml | loads 20 modules, ribbon | PRecAnnot.Plant3D2026.dll | legacy audit / CONFIRMED |
| 20 legacy DLL | legacy deployment | managed extensions | Settings.xml ordered list | commands and ribbon behavior | consolidated dependencies | legacy audit / CONFIRMED |
| Network root | corporate file share | SMB | legacy installation source | read at install/update | catalog source only | audits / CONFIRMED |
| Local support directory | workstation | filesystem | cache, scripts and applications | local writes | configurable cache root | source audit / CONFIRMED |
| AutoCAD profile / autoload registration | workstation configuration | AutoCAD startup | startup LISP | unknown | explicit Next configuration | no installed proof / UNKNOWN |
| Plant project / Ribbon | Plant runtime | Plant/AutoCAD APIs | project and UI context | commands/transactions | retained through one entry assembly | legacy audit / CONFIRMED |
`UNKNOWN_BOOTSTRAP`: source comments name `acaddoc.lsp`, but its deployed content and registration were not observed. It must not be represented as confirmed automatic startup.
@@ -0,0 +1,14 @@
# AS-IS update sequence
```mermaid
sequenceDiagram
participant N as Network
participant U as All_Serv_InstallUpdater
participant I as All_Serv_Install
U->>N: compare MD5/control-sum data
U->>U: delete/copy local installer files
U->>I: start local installer
Note over U,I: Actual deployed updater entrypoint is INFERRED
```
The updater source performs MD5 comparison and replacement. It was not run and its complete recovery behavior is not established.
@@ -0,0 +1,6 @@
Claim,Evidence,Location,Confidence
Installer is visible synchronizer,Launcher source audit,migration/audits/launcher-source-architecture/architecture-summary.md,CONFIRMED
P_Serv_Starter generates NETLOAD LISP,Launcher source audit,migration/audits/launcher-source-architecture/launcher-to-p-rec-annot.md,CONFIRMED
OrthoLoader reads Settings and loads 20 modules,Legacy loading audit,migration/audits/p-rec-annot-legacy-loading/loading-chain.md,CONFIRMED
Automatic acaddoc startup,No deployed proof,architecture/autodesk-launcher/as-is/known-unknowns.md,UNKNOWN
Target local package/atomic activation,Design decision,architecture/autodesk-launcher/adr,ACCEPTED
1 Claim Evidence Location Confidence
2 Installer is visible synchronizer Launcher source audit migration/audits/launcher-source-architecture/architecture-summary.md CONFIRMED
3 P_Serv_Starter generates NETLOAD LISP Launcher source audit migration/audits/launcher-source-architecture/launcher-to-p-rec-annot.md CONFIRMED
4 OrthoLoader reads Settings and loads 20 modules Legacy loading audit migration/audits/p-rec-annot-legacy-loading/loading-chain.md CONFIRMED
5 Automatic acaddoc startup No deployed proof architecture/autodesk-launcher/as-is/known-unknowns.md UNKNOWN
6 Target local package/atomic activation Design decision architecture/autodesk-launcher/adr ACCEPTED
@@ -0,0 +1,13 @@
# Launcher Next target architecture
Launcher Next is a parallel .NET 8 foundation: one visible x64 application, immutable versioned local packages, manifest/hash validation, atomic activation, rollback pointer, explicit dry-run Plant launch preparation and structured local logs. It does not deploy, alter profiles, start AutoCAD or change legacy files in this foundation.
```text
Launcher.Next.App -> Application orchestration -> Infrastructure adapters
-> product discovery (registry abstraction)
-> package catalog / local synchronizer / versioned store
-> manifest validation / SHA-256
-> startup script builder -> Process launcher dry-run
```
For P_Rec_Annot, one local entry `PRecAnnot.Plant3D2026.dll` replaces legacy `OrthoLoader -> Settings.xml -> 20 NETLOAD` orchestration. Its internal dependencies are CLR dependencies; Autodesk assemblies remain supplied by AutoCAD.
@@ -0,0 +1,11 @@
# Compatibility matrix
| Dimension | Legacy | Next foundation | Pilot decision |
|---|---|---|---|
| UI | WinForms installer | WPF shell | parallel |
| Runtime | .NET Framework 4.7 | .NET 8 x64 | separate EXE |
| Plugin load | generated multi-NETLOAD | one local NETLOAD | validate first |
| Package source | network copy | manifest + local cache | no production UNC client yet |
| Offline | unknown | last valid local version | design policy |
| Rollback | manual/unknown | previous pointer | foundation tested |
| AutoCAD version | historical 2019 evidence | discovery for Plant 2026 | require runtime confirmation |
@@ -0,0 +1,12 @@
# Target component model
| Component | Responsibility | Boundary | Side effects |
|---|---|---|---|
| Launcher.Next.App | minimal WPF shell and view model | UI only | none until explicit action |
| Core | contracts, manifest models, validation policy | no WPF/registry/process/filesystem/Autodesk | none |
| Application | synchronize/activate/prepare workflows | ports only | none directly |
| Infrastructure | local files, hashes, catalog implementation | filesystem/network behind interfaces | staging/cache writes |
| Autodesk | registry discovery, script construction, ProcessStartInfo | OS/process boundary | dry-run only in foundation |
| Observability | redacted JSONL events | local logging | log file write |
Feature flag: `Legacy`, `NextPilot`, `NextPreferred`; default is `Legacy`. No production configuration changes occur without explicit authorization.
@@ -0,0 +1,36 @@
# Legacy compatibility and pilot contract
## Required user-facing behavior
| Legacy behavior | Preserve in Next | Pilot status |
|---|---|---|
| Product/project selection workflow | preserve after separate UX analysis | not implemented |
| Plant 3D version discovery | preserve through adapter/override | foundation adapter |
| Local corporate plugin availability | preserve through valid local cache | designed/tested locally |
| P_Rec commands and Ribbon | preserve through one managed entry assembly | plugin-owned, not launched here |
| Immediate legacy return | preserve `LauncherMode=Legacy` | designed |
| Other legacy plugins | retain existing chain until individually migrated | required |
## Can be removed only for migrated P_Rec_Annot
- OrthoLoader configuration fan-out and twenty sequential `NETLOAD` calls.
- Generated LISP/BAT as the package-loading mechanism, once explicit Next startup is runtime-proven.
- Per-file legacy copy replacing an active module directory.
They remain necessary for other legacy corporate plugins until those plugins have their own migration and acceptance evidence.
## Duplicated or avoidable work
The source shows installer recursive copy/delete, updater hash/copy work, generated LISP generation and twenty sequential module loads. The precise overlap and cost require measurement. Next reduces avoidable work by comparing manifests, reusing a valid local version, staging only a new version and loading one entry assembly. It must not claim that legacy runs these actions on every AutoCAD startup without runtime evidence.
## P_Rec_Annot pilot
The pilot is a dry-run contract only:
```text
discover Plant 3D 2026 -> validate fixture manifest -> local test-source staging
-> activate version pointer -> build one startup NETLOAD script
-> construct ProcessStartInfo -> do not start process
```
It uses a fixture manifest with a synthetic SHA-256 and a test-created placeholder file. No real bundle, Plant assembly, package cache or production catalog is included.
@@ -0,0 +1,11 @@
# Local cache
The configurable root defaults by policy to the corporate local support root:
```text
<SupportRoot>/Launcher/{config,logs,state,staging,rollback}
<SupportRoot>/Applications/<PackageId>/versions/<version>/
<SupportRoot>/Applications/<PackageId>/current.json
```
Keep `current`, `previous` and incomplete `staging` separate. Activated directories are immutable; do not overwrite a DLL used by AutoCAD. A valid cache permits offline use; an invalid or partial staging directory never becomes current.
@@ -0,0 +1,9 @@
# Migration strategy
1. Preserve legacy unchanged and default `LauncherMode=Legacy`.
2. Pilot an isolated group with `NextPilot`, local cache and one P_Rec entry assembly.
3. Validate package/hash/rollback and dry-run launch preparation before any runtime use.
4. Test actual bootstrap only with separate authorization and non-production Plant data.
5. Move to `NextPreferred` only after operational evidence; retain immediate Legacy fallback.
No AppLoader, registry demand-loading or direct UNC deployment decision is implied by this foundation.
@@ -0,0 +1,5 @@
# Observability
Write best-effort JSONL records under `%LOCALAPPDATA%\Promhimproekt\LauncherNext\Logs`. Events: `LauncherStarted`, `ConfigurationLoaded`, `ProductDiscoveryStarted`, `ProductDiscovered`, `CatalogReadStarted`, `CatalogReadCompleted`, `PackageValidationStarted`, `PackageValidationCompleted`, `SynchronizationStarted`, `SynchronizationCompleted`, `ActivationCompleted`, `RollbackStarted`, `RollbackCompleted`, `LaunchPrepared`, `LaunchBlocked`, `LauncherFailed`.
Records exclude user identity, tokens, passwords, full Plant project paths, project numbers and engineering-file content. Logs must never block a launch decision.
@@ -0,0 +1,26 @@
# Package manifest
```json
{
"schemaVersion": 1,
"packageId": "P_Rec_Annot",
"version": "2026.1.0",
"product": "Plant3D",
"productYear": 2026,
"architecture": "x64",
"minimumLauncherVersion": "1.0.0",
"compatibleAutoCadSeries": ["R25.1"],
"requiredRuntime": ".NET 8",
"entryAssembly": "PRecAnnot.Plant3D2026.dll",
"files": [{ "path": "PRecAnnot.Plant3D2026.dll", "length": 0, "sha256": "<64 hex>" }],
"dependencies": [],
"optionalResources": [],
"rolloutChannel": "pilot",
"disabled": false,
"rollbackSafe": true,
"releaseNotesReference": "relative-release-notes.md",
"startup": { "type": "netload", "command": "PHP_REC_ANNOT" }
}
```
Validation rejects absolute/traversal/duplicate paths, missing entry assembly, incompatible product/year/architecture, and Autodesk/Plant managed assemblies (`AcMgd`, `AcDbMgd`, `AcCoreMgd`, `AdWindows`, `PnP*`, `Autodesk.ProcessPower*`). No absolute paths belong in a manifest.
@@ -0,0 +1,5 @@
# Plugin bootstrap
Generate a temporary, locally stored startup script only from a validated package root. It contains exactly one escaped `NETLOAD` for the entry assembly and no separate dependency loads. The script is not executed by foundation tests.
`PRecAnnot.Plant3D2026.dll` is the only managed entry. `PRecAnnot.Application`, `PRecAnnot.Core` and allowed third-party libraries resolve as CLR dependencies. Autodesk managed assemblies are neither copied nor included in package manifests; they load from the installed AutoCAD/Plant runtime.
@@ -0,0 +1,9 @@
# Target security model
- Load managed assemblies only from validated local version directories, never directly from UNC.
- Hash every packaged file with SHA-256 before activation.
- Validate package paths against traversal and package-root escape.
- Forbid Autodesk runtime DLLs in packages.
- Detect Autodesk installations through a read-only registry abstraction with configuration override for diagnostics.
- Do not change `TRUSTEDPATHS`, registry, profiles or production deployment without explicit approval.
- Keep credentials/secrets out of source, manifests and telemetry.
@@ -0,0 +1,22 @@
# Target startup sequence
```mermaid
sequenceDiagram
participant U as User
participant L as Launcher Next
participant D as Product discovery
participant C as Package catalog
participant S as Local store
participant A as Plant 3D
participant P as PRecAnnot entry
U->>L: Prepare launch (NextPilot)
L->>D: discover installed Plant 3D
L->>C: read package manifest when network is available
L->>S: stage, hash-validate, activate package atomically
L->>L: build one validated NETLOAD startup script
L->>A: ProcessStartInfo (explicit profile/script)
A->>P: one NETLOAD local PRecAnnot.Plant3D2026.dll
P->>A: commands and Ribbon registry
```
Foundation performs through `LaunchPrepared`; real process start remains dry-run only.
@@ -0,0 +1,10 @@
# Atomic update and rollback
1. Read and validate manifest.
2. Copy/download into a new staging directory with cancellation and bounded parallelism.
3. Validate file length, SHA-256, required files, entry metadata and forbidden DLLs.
4. Write local manifest, close streams and rename staging to `versions/<version>`.
5. Atomically replace `current.json`; preserve prior pointer.
6. On bootstrap failure, offer rollback by atomically restoring the prior pointer.
Never update an active directory in place. Locked files leave the current version intact and yield a diagnostic status. Network failure uses the last valid local version; when none exists, policy is to launch Plant without the corporate plugin or offer Legacy fallback, never a partial Next package.
@@ -0,0 +1,11 @@
# Launcher source architecture audit
Static, read-only audit of three independent nested Git repositories under `C:\dev\launcher\git` and the linked bootstrap repository `C:\dev\p_serv_starter`. No launcher executable, helper, build, installer, registry action, profile change, or AutoCAD process was run.
The three projects are all independent WinForms `WinExe` applications, not a three-project solution: `All_Serv_Install`, `All_Serv_InstallUpdater`, and `All_Serv_Load`. Each solution contains exactly one project and has Debug/Release AnyCPU `Build.0` enabled. There are no project references between them.
The current primary installer is `All_Serv_Install.exe` (v1.1.0.0). It presents a Form1 UI, reads server-side settings, copies selected corporate application directories to local targets, then starts a configured external executable. `All_Serv_InstallUpdater.exe` is a visible but short-lived self-updater: it updates the local installer with MD5 checks and starts `All_Serv_Install.exe`. `All_Serv_Load.exe` is an earlier separate WinForms loader (v1.0.0.0); source evidence does not establish that it is part of the current primary workflow.
No static source evidence shows a direct reference to `app_netload.lsp`, `OrthoLoader.dll`, or `P_Rec_Annot` in the updater. The installer configuration does establish `P_Rec_Annot` deployment: catalog `P_Rec_Annot`, install file `OrthoLoader.dll`, source DriveA, target `C:\AutoCAD User Support\Applications`.
`P_Serv_Starter.exe` is the confirmed external bootstrap selected by the Plant 3D installer pattern. Its source creates `_app_netload.lsp` from the installer `Settings.xml`, copies `launcher_starter.lsp` to the local Applications directory, and launches the configured local `start_plant_3d.bat`. The LISP renames the generated file to `app_netload.lsp`, adds the local Applications directory to SupportPath, and loads it. The source comment attributes LISP auto-start to `acaddoc.lsp`, but that file and the expected local installation were absent on this workstation during the audit; automatic startup remains `UNKNOWN`.
@@ -0,0 +1,16 @@
# Application synchronization analysis
`All_Serv_Install` is a synchronizing installer, not merely a launcher. It reads server `Settings.xml`, selects products/plugins, and recursively copies application directories from configured network roots to local targets. Its copy path includes directory deletion and file deletion (`Form1.cs:396-645`), so it was not executed for this audit.
For Plant 3D_2019, the configuration specifies DriveA and DriveQ network sources and local target `C:\AutoCAD User Support\Applications`. The catalog `P_Rec_Annot` is selected with install entry `OrthoLoader.dll`; `download_list_plant.xml` maps the P_Rec_Annot plugin ID to a real area. This proves the intended local deployment source and target, but not the state of a particular workstation installation.
`All_Serv_InstallUpdater` is a separate local updater. Its `Form1_Load` computes MD5 checksums, compares a server control-sums manifest, deletes stale local files, copies changed files, writes a local control-sums file and starts `All_Serv_Install.exe`. It references `N:\BIM\save\All_Serv_Install` and `N:\BIM\data_write\All_Serv_Install`; it can write both local and server test/control files. It was not executed.
| Component | Source | Target | Version check | Destructive operations | Role | Confidence |
|---|---|---|---|---|---|---|
| All_Serv_Install | configured network DriveA/DriveQ | configured local target | configuration-driven selection; no content hash established | recursive copy, delete files/directories | application synchronizer | CONFIRMED |
| All_Serv_InstallUpdater | server installer directories | updater own local directory | MD5 control sums | deletes stale local files; writes control sums | installer updater | CONFIRMED |
| P_Serv_Starter | local installed executable + installer Settings.xml | local Applications | none | overwrites `_app_netload.lsp` and `launcher_starter.lsp` | per-launch bootstrap | CONFIRMED |
Offline behavior is `UNKNOWN`: both installer and updater require configured paths at the point they run, but source evidence does not show an explicit offline fallback.
@@ -0,0 +1,47 @@
# Architecture summary
## Project architecture
```text
All_Serv_InstallUpdater.exe (independent repository / updater)
-> starts local All_Serv_Install.exe
All_Serv_Install.exe (independent repository / visible installer and synchronizer)
-> starts configured P_Serv_Starter.exe
P_Serv_Starter.exe (separate repository / Plant bootstrap)
-> generates local _app_netload.lsp
-> copies launcher_starter.lsp
-> starts configured local start_plant_3d.bat
All_Serv_Load.exe (independent repository)
-> separate historical/alternate loader; no source dependency edge to the above
```
There are no ProjectReference edges between the three projects beneath `C:\dev\launcher\git`; each is its own one-project solution. `P_Serv_Starter` is a fourth independent repository selected by configuration, not a project reference. This explains why a user can experience one product despite multiple executables: the updater can hand off to the visible installer, which synchronizes selected applications and delegates Plant startup to a hidden bootstrap. Actual deployed shortcut/entrypoint evidence was not present, so the updater's use in a specific installation remains `INFERRED`.
## Roles and status
| Project | Output | Role | Status | Evidence |
|---|---|---|---|---|
| All_Serv_Install | WinExe | visible catalog UI, synchronizer and configured process launcher | ACTIVE_PRIMARY | Form1 startup and copy/Process.Start code |
| All_Serv_InstallUpdater | WinExe | installer self-updater then handoff | ACTIVE_UPDATER | Form1_Load MD5/copy/start code |
| All_Serv_Load | WinExe | independent alternate/historical loader | UNKNOWN | independent Form1 and 2019 batch script; no current deployment proof |
| P_Serv_Starter | Exe | Plant session bootstrap | ACTIVE_BOOTSTRAP | selected in installer XML; writes loader and starts batch |
## Runtime chain
The confirmed chain ends at execution of the local Plant start batch. The gap between that batch and `launcher_starter.lsp` automatic loading is `UNKNOWN`: no local batch, `acaddoc.lsp`, or Applications files were found. Once `launcher_starter.lsp` runs, all following load steps are source-confirmed.
## Answers to the requested questions
1. The three launcher projects are independent WinForms executables: installer, updater and alternate loader.
2. `All_Serv_Install` creates the confirmed visible catalog UI.
3. Three EXE artifacts are declared by the launcher repositories; P_Serv_Starter adds one external bootstrap EXE.
4. No launcher project is a class library; `launcher_starter.lsp` is a copied runtime content artifact.
5. Updater/helper/bootstrap behavior is confirmed in source; actual installed-process count is unknown.
6. Plant launch is delegated by `All_Serv_Install` to configured `P_Serv_Starter.exe`; the bootstrap starts a local batch. A current Plant executable/profile/argument string is not proven.
7. `app_netload.lsp` and `OrthoLoader.dll` are connected through P_Serv_Starter generated LISP and installer plugin metadata.
8. The installer synchronizes corporate application folders to a local Applications cache; it does not load P_Rec_Annot directly.
9. No source proof warrants modifying LAUNCHER for the new plugin yet. First validate one local entry assembly through the established catalog/bootstrap contract in a non-production pilot.
@@ -0,0 +1,38 @@
# AutoCAD/Plant launch chain
## Confirmed static chain
```text
User
-> All_Serv_Install.exe Form1
-> copy selected Plant 3D application folders to local Applications target
-> Process.Start(P_Serv_Starter.exe, five configured arguments)
-> P_Serv_Starter creates local _app_netload.lsp and copies launcher_starter.lsp
-> P_Serv_Starter starts C:\AutoCAD User Support\p3d-support\start_plant_3d.bat
-> [UNKNOWN: installed batch contents and auto-load registration]
-> Plant 3D startup
-> launcher_starter.lsp (when loaded)
-> local app_netload.lsp
-> _netload local Applications\P_Rec_Annot\OrthoLoader.dll
-> OrthoLoader Settings.xml -> sequential ._netload configured modules
```
The final two steps are confirmed by the prior P_Rec_Annot legacy audit. The activation of `launcher_starter.lsp` is not directly implemented in `P_Serv_Starter`: `Program.cs:181` comments that `acaddoc.lsp` renames the generated file, while the copied LISP itself performs the rename and `load`. No matching installed `acaddoc.lsp`, batch file, or Applications directory existed on this workstation at audit time. Therefore automatic execution is `UNKNOWN`, not asserted.
## Source evidence
| Claim | Evidence | Confidence |
|---|---|---|
| User-visible installer starts configured object | `All_Serv_Install/Form1.cs:195-230` | CONFIRMED |
| Plant pattern object is `P_Serv_Starter.exe` | `All_Serv_Install/Settings.xml`, Plant 3D pattern | CONFIRMED |
| Five arguments include product, Applications target, settings, project and working directory | `Settings.xml` Plant pattern and `P_Serv_Starter/Program.cs:12-23` | CONFIRMED |
| Bootstrap generates `_app_netload.lsp` | `P_Serv_Starter/Program.cs:58-182` | CONFIRMED |
| Generated DLL modules use AutoLISP `_netload` | `P_Serv_Starter/Program.cs:146-151` | CONFIRMED |
| Bootstrap copies LISP and starts local batch file | `P_Serv_Starter/Program.cs:202-216,49-54` | CONFIRMED |
| Generated LISP is renamed, SupportPath updated and loaded | `P_Serv_Starter/launcher_starter.lsp:74-96` | CONFIRMED |
| Actual installed batch launches a specific Plant version | no installed batch was present | UNKNOWN |
## Historical source-only alternative
`All_Serv_Load/Addins/start_plant_3d.bat` contains a direct AutoCAD 2019 command using `/product PLNT3D /language "ru-RU"`, preceded by PlantCleaner and PnPPersistentCache cleanup. It is evidence only of that project's historical script; it is not evidence that current users start Plant 3D 2026 this way. No `/ld`, profile, LISP or script argument is present in that script.
@@ -0,0 +1,7 @@
Artifact,SourceProject,Type,AssemblyName,AssemblyVersion,TargetFramework,Architecture,ReferencedBy,VisibleToUser,RequiredAtRuntime,LikelyRole,Confidence
All_Serv_Install.exe,All_Serv_Install,WinExe,All_Serv_Install,1.1.0.0,.NETFramework v4.7,AnyCPU,None,Yes,Yes,PRIMARY_INSTALLER,CONFIRMED
All_Serv_InstallUpdater.exe,All_Serv_InstallUpdater,WinExe,All_Serv_InstallUpdater,3.0.0.0,.NETFramework v4.7,AnyCPU,None,Briefly,Only when updater is launched,UPDATER_HELPER,CONFIRMED
All_Serv_Load.exe,All_Serv_Load,WinExe,All_Serv_Load,1.0.0.0,.NETFramework v4.8,AnyCPU,None,Possible,Unknown,SEPARATE_LEGACY_LOADER,INFERRED
P_Serv_Starter.exe,P_Serv_Starter,Exe,P_Serv_Starter,3.0.1.0,.NETFramework v4.7,AnyCPU,Configured external process from All_Serv_Install,No,Yes for Plant launch bootstrap,BOOTSTRAP,CONFIRMED
launcher_starter.lsp,P_Serv_Starter,Content,LISP script,N/A,N/A,N/A,Copied by P_Serv_Starter,No,Yes for generated loader activation,LISP_BOOTSTRAP,CONFIRMED
1 Artifact SourceProject Type AssemblyName AssemblyVersion TargetFramework Architecture ReferencedBy VisibleToUser RequiredAtRuntime LikelyRole Confidence
2 All_Serv_Install.exe All_Serv_Install WinExe All_Serv_Install 1.1.0.0 .NETFramework v4.7 AnyCPU None Yes Yes PRIMARY_INSTALLER CONFIRMED
3 All_Serv_InstallUpdater.exe All_Serv_InstallUpdater WinExe All_Serv_InstallUpdater 3.0.0.0 .NETFramework v4.7 AnyCPU None Briefly Only when updater is launched UPDATER_HELPER CONFIRMED
4 All_Serv_Load.exe All_Serv_Load WinExe All_Serv_Load 1.0.0.0 .NETFramework v4.8 AnyCPU None Possible Unknown SEPARATE_LEGACY_LOADER INFERRED
5 P_Serv_Starter.exe P_Serv_Starter Exe P_Serv_Starter 3.0.1.0 .NETFramework v4.7 AnyCPU Configured external process from All_Serv_Install No Yes for Plant launch bootstrap BOOTSTRAP CONFIRMED
6 launcher_starter.lsp P_Serv_Starter Content LISP script N/A N/A N/A Copied by P_Serv_Starter No Yes for generated loader activation LISP_BOOTSTRAP CONFIRMED
@@ -0,0 +1,7 @@
Solution,Configuration,Platform,Project,BuildEnabled,Notes
All_Serv_Install.sln,Debug,Any CPU,All_Serv_Install,yes,Only project in solution
All_Serv_Install.sln,Release,Any CPU,All_Serv_Install,yes,Only project in solution
All_Serv_InstallUpdater.sln,Debug,Any CPU,All_Serv_InstallUpdater,yes,Only project in solution
All_Serv_InstallUpdater.sln,Release,Any CPU,All_Serv_InstallUpdater,yes,Only project in solution
All_Serv_Load.sln,Debug,Any CPU,All_Serv_Load,yes,Only project in solution
All_Serv_Load.sln,Release,Any CPU,All_Serv_Load,yes,Only project in solution
1 Solution Configuration Platform Project BuildEnabled Notes
2 All_Serv_Install.sln Debug Any CPU All_Serv_Install yes Only project in solution
3 All_Serv_Install.sln Release Any CPU All_Serv_Install yes Only project in solution
4 All_Serv_InstallUpdater.sln Debug Any CPU All_Serv_InstallUpdater yes Only project in solution
5 All_Serv_InstallUpdater.sln Release Any CPU All_Serv_InstallUpdater yes Only project in solution
6 All_Serv_Load.sln Debug Any CPU All_Serv_Load yes Only project in solution
7 All_Serv_Load.sln Release Any CPU All_Serv_Load yes Only project in solution
@@ -0,0 +1,10 @@
Setting,OwnerProject,Source,DefaultValue,RuntimeValueSource,Purpose,Sensitive,Required,Fallback,UsedBy,Confidence
InstallerSettings,All_Serv_Install,N:\BIM\save\All_Serv_Install\Settings.xml,None,hard-coded source path,product/plugin catalog and process pattern,No,Yes,None,Form1,CONFIRMED
PlantApplicationTarget,All_Serv_Install,Settings.xml Plant pattern,C:\AutoCAD User Support\Applications,XML target,local corporate applications directory,No,Yes,None,copy and P_Serv_Starter arguments,CONFIRMED
PlantBootstrapExecutable,All_Serv_Install,Settings.xml Plant pattern,C:\CAD User Support\All_Serv_Install\P_Serv_Starter.exe,XML object,external bootstrap process,No,Yes,None,Form1 Process.Start,CONFIRMED
PluginCatalog,All_Serv_Install,Settings.xml plugin entry,P_Rec_Annot,XML plugin metadata,subdirectory to deploy,No,Optional by selection,skip if unselected,copy and generated loader,CONFIRMED
PluginInstall,All_Serv_Install,Settings.xml plugin entry,OrthoLoader.dll,XML plugin metadata,first P_Rec_Annot assembly to load,No,Optional by selection,skip if catalog unavailable,P_Serv_Starter generated LISP,CONFIRMED
GeneratedLoader,P_Serv_Starter,arguments plus installer Settings.xml,_app_netload.lsp,computed at runtime,per-session module loading commands,Contains project path in first comment,Yes,abort on error,generated LISP,CONFIRMED
PlantStartScript,P_Serv_Starter,Program.cs,C:\AutoCAD User Support\p3d-support\start_plant_3d.bat,hard-coded source constant,launch Plant process,No,Yes,none,Process.Start,CONFIRMED
UpdaterServer,All_Serv_InstallUpdater,Form1.cs,N:\BIM\data_write\All_Serv_Install,hard-coded source constant,hash/copy source,No,Yes,none,Form1_Load,CONFIRMED
1 Setting OwnerProject Source DefaultValue RuntimeValueSource Purpose Sensitive Required Fallback UsedBy Confidence
2 InstallerSettings All_Serv_Install N:\BIM\save\All_Serv_Install\Settings.xml None hard-coded source path product/plugin catalog and process pattern No Yes None Form1 CONFIRMED
3 PlantApplicationTarget All_Serv_Install Settings.xml Plant pattern C:\AutoCAD User Support\Applications XML target local corporate applications directory No Yes None copy and P_Serv_Starter arguments CONFIRMED
4 PlantBootstrapExecutable All_Serv_Install Settings.xml Plant pattern C:\CAD User Support\All_Serv_Install\P_Serv_Starter.exe XML object external bootstrap process No Yes None Form1 Process.Start CONFIRMED
5 PluginCatalog All_Serv_Install Settings.xml plugin entry P_Rec_Annot XML plugin metadata subdirectory to deploy No Optional by selection skip if unselected copy and generated loader CONFIRMED
6 PluginInstall All_Serv_Install Settings.xml plugin entry OrthoLoader.dll XML plugin metadata first P_Rec_Annot assembly to load No Optional by selection skip if catalog unavailable P_Serv_Starter generated LISP CONFIRMED
7 GeneratedLoader P_Serv_Starter arguments plus installer Settings.xml _app_netload.lsp computed at runtime per-session module loading commands Contains project path in first comment Yes abort on error generated LISP CONFIRMED
8 PlantStartScript P_Serv_Starter Program.cs C:\AutoCAD User Support\p3d-support\start_plant_3d.bat hard-coded source constant launch Plant process No Yes none Process.Start CONFIRMED
9 UpdaterServer All_Serv_InstallUpdater Form1.cs N:\BIM\data_write\All_Serv_Install hard-coded source constant hash/copy source No Yes none Form1_Load CONFIRMED
@@ -0,0 +1,7 @@
Artifact,ExpectedInstalledLocation,SourceProject,SourceAssemblyVersion,InstalledBinaryFound,InstalledMetadataMatch,MatchStatus,Notes
All_Serv_Install.exe,C:\CAD User Support\All_Serv_Install\All_Serv_Install.exe,All_Serv_Install,1.1.0.0,False,N/A,NO_DEPLOYED_BINARY_FOUND,Expected location came from Settings.xml object directory
P_Serv_Starter.exe,C:\CAD User Support\All_Serv_Install\P_Serv_Starter.exe,P_Serv_Starter,3.0.1.0,False,N/A,NO_DEPLOYED_BINARY_FOUND,Expected location came from Settings.xml object
All_Serv_InstallUpdater.exe,UNKNOWN,All_Serv_InstallUpdater,3.0.0.0,False,N/A,NO_DEPLOYED_BINARY_FOUND,No installed location in inspected source
All_Serv_Load.exe,UNKNOWN,All_Serv_Load,1.0.0.0,False,N/A,NO_DEPLOYED_BINARY_FOUND,No installed location in inspected source
start_plant_3d.bat,C:\AutoCAD User Support\p3d-support\start_plant_3d.bat,P_Serv_Starter dependency,N/A,False,N/A,NO_DEPLOYED_FILE_FOUND,Not present at expected path during audit
1 Artifact ExpectedInstalledLocation SourceProject SourceAssemblyVersion InstalledBinaryFound InstalledMetadataMatch MatchStatus Notes
2 All_Serv_Install.exe C:\CAD User Support\All_Serv_Install\All_Serv_Install.exe All_Serv_Install 1.1.0.0 False N/A NO_DEPLOYED_BINARY_FOUND Expected location came from Settings.xml object directory
3 P_Serv_Starter.exe C:\CAD User Support\All_Serv_Install\P_Serv_Starter.exe P_Serv_Starter 3.0.1.0 False N/A NO_DEPLOYED_BINARY_FOUND Expected location came from Settings.xml object
4 All_Serv_InstallUpdater.exe UNKNOWN All_Serv_InstallUpdater 3.0.0.0 False N/A NO_DEPLOYED_BINARY_FOUND No installed location in inspected source
5 All_Serv_Load.exe UNKNOWN All_Serv_Load 1.0.0.0 False N/A NO_DEPLOYED_BINARY_FOUND No installed location in inspected source
6 start_plant_3d.bat C:\AutoCAD User Support\p3d-support\start_plant_3d.bat P_Serv_Starter dependency N/A False N/A NO_DEPLOYED_FILE_FOUND Not present at expected path during audit
@@ -0,0 +1,4 @@
EntryPoint,Project,File,Lines,ApplicationType,CommandLineArguments,CreatesVisibleWindow,CanRunHeadless,StartsOtherProcesses,SingleInstanceMechanism,ExitBehavior,Confidence
Program.Main,All_Serv_Install,All_Serv_Install/Program.cs,13-18,WinForms,none parsed,yes Form1,no,yes configured object,none,Application exits after launching configured object,CONFIRMED
Program.Main,All_Serv_InstallUpdater,All_Serv_InstallUpdater/Program.cs,13-18,WinForms,none parsed,yes Form1,no,yes All_Serv_Install.exe,none,exits after updater starts installer,CONFIRMED
Program.Main,All_Serv_Load,All_Serv_Load/All_Serv_Load/Program.cs,12-17,WinForms,none parsed,yes Form1,no,yes configured object,none,Application exits after launching configured object,CONFIRMED
1 EntryPoint Project File Lines ApplicationType CommandLineArguments CreatesVisibleWindow CanRunHeadless StartsOtherProcesses SingleInstanceMechanism ExitBehavior Confidence
2 Program.Main All_Serv_Install All_Serv_Install/Program.cs 13-18 WinForms none parsed yes Form1 no yes configured object none Application exits after launching configured object CONFIRMED
3 Program.Main All_Serv_InstallUpdater All_Serv_InstallUpdater/Program.cs 13-18 WinForms none parsed yes Form1 no yes All_Serv_Install.exe none exits after updater starts installer CONFIRMED
4 Program.Main All_Serv_Load All_Serv_Load/All_Serv_Load/Program.cs 12-17 WinForms none parsed yes Form1 no yes configured object none Application exits after launching configured object CONFIRMED
@@ -0,0 +1,11 @@
Generated: 2026-07-31
Method: static read-only source and metadata inspection; no build or executable launch.
Launcher sources:
- C:\dev\launcher\git\All_Serv_Install @ 29d163322a2dc01983e15bc26f62f787fb402a04 (main; clean)
- C:\dev\launcher\git\All_Serv_InstallUpdater @ 1e6f2e78a1ca0005ee704dbeb464e73a478c3390 (main; clean)
- C:\dev\launcher\git\All_Serv_Load @ bd38b87f903b02533dbb659d9776cd32dd9e2a3e (main; clean)
- C:\dev\p_serv_starter @ 8219520aad1a5ccc2cd96611f2a71fef61cbf9b1 (main; clean)
Local deployment probes were metadata-only. Expected launcher/bootstrap files and the relevant AutoCAD User Support scripts were absent at the configured locations at audit time.
No source, binaries, user configuration content, registry export, DWG, DCF, Project.xml or network files are included in this audit.
@@ -0,0 +1,24 @@
# LAUNCHER to P_Rec_Annot
## Proven loading contract
| Stage | Evidence | Finding | Confidence |
|---|---|---|---|
| Deployment selection | `All_Serv_Install/Settings.xml`; `download_lists/download_list_plant.xml` | `P_Rec_Annot` is an installer plugin catalog; its configured install file is `OrthoLoader.dll`. | CONFIRMED |
| Local target | Plant configuration target | P_Rec_Annot is intended to be copied under `C:\AutoCAD User Support\Applications\P_Rec_Annot`. | CONFIRMED |
| Bootstrap selection | installer Plant pattern object/arguments | `All_Serv_Install` invokes `P_Serv_Starter.exe` after install selection. | CONFIRMED |
| Generated first load | `P_Serv_Starter/Program.cs:140-174` | For each installed DLL catalog, generated LISP emits `(command "_netload" attribute)`. Therefore `OrthoLoader.dll` is loaded from the local Applications path when its catalog exists. | CONFIRMED |
| Generated LISP activation | `launcher_starter.lsp:74-96` | It renames `_app_netload.lsp`, adds Applications to SupportPath and `(load ...)`s `app_netload.lsp`. | CONFIRMED |
| Automatic LISP invocation | source comment in `Program.cs:181`; no installed startup file | `acaddoc.lsp` is named by a comment, but its installed content and registration were not available. | UNKNOWN |
| Secondary module load | legacy audit, `OrthoLoader.Ribbon.Initialize` / `OrthoLoader.Initialize` | OrthoLoader reads adjacent Settings.xml and sequentially calls `._netload` for 20 configured modules. | CONFIRMED |
## P_Rec_Annot-specific implication
The historic contract is not a direct network `NETLOAD`. It is a local synchronized application directory followed by a generated, per-session AutoLISP loader. A generated LISP includes the Plant project path in its first comment and conditionally emits loads only for existing local plugin catalogs. New code must not reproduce that behavior before deciding whether it is compatible, necessary and safe for Plant 3D 2026.
The prior legacy audit established that `OrthoLoader.dll` is the initial assembly for P_Rec_Annot. This launcher audit establishes how the installer/bootstrap is intended to make that assembly loadable. Neither audit proves the deployed local files existed or auto-loaded on this workstation.
## Integration recommendation
Use one versioned, locally synchronized entry artifact only after a compatibility decision. The minimal pilot should retain the existing installer catalog/target contract, make the bootstrap load exactly one new entry assembly, and use an explicitly reviewed startup mechanism. Do not rely on an unproven AppLoader bundle or add new global `TRUSTEDPATHS`/profile mutations. The generated loader and any project-path comment are legacy behavior, not requirements for the new P_Rec_Annot until runtime validation establishes a need.
@@ -0,0 +1,6 @@
ParentProject,ParentFile,ParentMethod,Executable,Arguments,WorkingDirectory,UseShellExecute,WaitForExit,Conditions,ErrorHandling,Confidence
All_Serv_Install,All_Serv_Install/Form1.cs,button launch handler around 195-230,configured Pattern.Object,Predictable quoted Pattern.Attrib values including project path,not set,default,not set,after successful plugin copy,none visible around Process.Start,CONFIRMED
All_Serv_InstallUpdater,All_Serv_InstallUpdater/Form1.cs,Form1_Load 28-48,local All_Serv_Install.exe,none,not set,default,not set,after successful MD5 update,closes on update failure,CONFIRMED
All_Serv_Load,All_Serv_Load/Form1.cs,launch handler around 180-205,configured Pattern.Object,configured attributes plus selected project path,not set,default,not set,after successful plugin copy,none visible around Process.Start,CONFIRMED
P_Serv_Starter,P_Serv_Starter/Program.cs,Main 15-54,C:\AutoCAD User Support\p3d-support\start_plant_3d.bat,none,changed to configured project directory before start,default,not set,after local loader generation and LISP copy,shows message and returns for loader/copy errors,CONFIRMED
start_plant_3d.bat,All_Serv_Load/Addins/start_plant_3d.bat,script,C:\Program Files\Autodesk\AutoCAD 2019\acad.exe,/product PLNT3D /language ru-RU,not set,START default,no,after PlantCleaner,script has no error checks,CONFIRMED
1 ParentProject ParentFile ParentMethod Executable Arguments WorkingDirectory UseShellExecute WaitForExit Conditions ErrorHandling Confidence
2 All_Serv_Install All_Serv_Install/Form1.cs button launch handler around 195-230 configured Pattern.Object Predictable quoted Pattern.Attrib values including project path not set default not set after successful plugin copy none visible around Process.Start CONFIRMED
3 All_Serv_InstallUpdater All_Serv_InstallUpdater/Form1.cs Form1_Load 28-48 local All_Serv_Install.exe none not set default not set after successful MD5 update closes on update failure CONFIRMED
4 All_Serv_Load All_Serv_Load/Form1.cs launch handler around 180-205 configured Pattern.Object configured attributes plus selected project path not set default not set after successful plugin copy none visible around Process.Start CONFIRMED
5 P_Serv_Starter P_Serv_Starter/Program.cs Main 15-54 C:\AutoCAD User Support\p3d-support\start_plant_3d.bat none changed to configured project directory before start default not set after local loader generation and LISP copy shows message and returns for loader/copy errors CONFIRMED
6 start_plant_3d.bat All_Serv_Load/Addins/start_plant_3d.bat script C:\Program Files\Autodesk\AutoCAD 2019\acad.exe /product PLNT3D /language ru-RU not set START default no after PlantCleaner script has no error checks CONFIRMED
@@ -0,0 +1,15 @@
# Project dependency graph
```text
All_Serv_InstallUpdater.exe
-> local All_Serv_Install.exe (Process.Start)
All_Serv_Install.exe
-> configured external object: P_Serv_Starter.exe for Plant 3D 2019
-> copies selected application catalogs from configured network sources
All_Serv_Load.exe
-> configured external object (separate legacy workflow)
```
No `ProjectReference` exists in any of the three projects. Each solution has a single buildable project. `P_Serv_Starter.exe` is referenced only as a configured external object, not supplied by any audited launcher project.
@@ -0,0 +1,10 @@
# Repositories
| Repository | HEAD | Branch | Dirty | Remote | Submodules | Confidence |
|---|---|---|---|---|---|---|
| All_Serv_Install | `29d163322a2dc01983e15bc26f62f787fb402a04` | main | no | GitHub `pxpgitorg/All_Serv_Install` | none | CONFIRMED |
| All_Serv_InstallUpdater | `1e6f2e78a1ca0005ee704dbeb464e73a478c3390` | main | no | GitHub `pxpgitorg/All_Serv_InstallUpdater` | none | CONFIRMED |
| All_Serv_Load | `bd38b87f903b02533dbb659d9776cd32dd9e2a3e` | main | no | GitHub `pxpgitorg/All_Serv_Load` | none | CONFIRMED |
| P_Serv_Starter | `8219520aad1a5ccc2cd96611f2a71fef61cbf9b1` | main | no | GitHub `pxpgitorg/P_Serv_Starter` | none | CONFIRMED |
The launcher parent directory is not a Git repository; it contains three nested repositories. `P_Serv_Starter` is a fourth, separately supplied repository outside that parent and is referenced by installer configuration. No launcher or bootstrap source was modified.
@@ -0,0 +1,5 @@
Project,ProjectPath,ProjectGuid,ProjectType,Sdk,OutputType,TargetFramework,PlatformTarget,AssemblyName,RootNamespace,StartupObject,EntryPoint,ApplicationIcon,ApplicationManifest,ProjectReferences,PackageReferences,DirectDllReferences,BuildOutput,PublishOutput,IsStartupProject,HasMainMethod,HasAppXaml,HasFormsEntryPoint,HasPostBuildActions,LikelyRole,Status
All_Serv_Install,All_Serv_Install/All_Serv_Install.csproj,{FE649634-187B-410E-9A53-A8C41134F53D},C# classic MSBuild,,WinExe,v4.7,AnyCPU,All_Serv_Install,All_Serv_Install,,Program.Main,,,none,none,System assemblies,bin/Debug or bin/Release,none,only solution project,yes,no,yes,no,primary corporate application installer/synchronizer,ACTIVE_PRIMARY
All_Serv_InstallUpdater,All_Serv_InstallUpdater/All_Serv_InstallUpdater.csproj,{6E075C69-20B5-4613-8BE1-5C41A615C70F},C# classic MSBuild,,WinExe,v4.7,AnyCPU,All_Serv_InstallUpdater,All_Serv_InstallUpdater,,Program.Main,,,none,none,System assemblies,bin/Debug or bin/Release,none,only solution project,yes,no,yes,no,self-updater for All_Serv_Install,ACTIVE_UPDATER
All_Serv_Load,All_Serv_Load/All_Serv_Load/All_Serv_Load.csproj,{CCEF88B4-9157-4F50-AA7F-E0A6F3A665E3},C# classic MSBuild,,WinExe,v4.8,AnyCPU,All_Serv_Load,All_Serv_Load,,Program.Main,,,none,none,System assemblies,bin/Debug or bin/Release,none,only solution project,yes,no,yes,no,earlier standalone loader/install UI,UNKNOWN
P_Serv_Starter,P_Serv_Starter/P_Serv_Starter.csproj,{DBA449EE-5DEC-454B-9EF1-E71BF949D880},C# classic MSBuild,,Exe,v4.7,AnyCPU,P_Serv_Starter,P_Serv_Starter,,Program.Main,,,none,none,System assemblies,bin/Debug or bin/Release,none,only solution project,yes,no,no,no,external Plant bootstrap selected by installer configuration,ACTIVE_BOOTSTRAP
1 Project ProjectPath ProjectGuid ProjectType Sdk OutputType TargetFramework PlatformTarget AssemblyName RootNamespace StartupObject EntryPoint ApplicationIcon ApplicationManifest ProjectReferences PackageReferences DirectDllReferences BuildOutput PublishOutput IsStartupProject HasMainMethod HasAppXaml HasFormsEntryPoint HasPostBuildActions LikelyRole Status
2 All_Serv_Install All_Serv_Install/All_Serv_Install.csproj {FE649634-187B-410E-9A53-A8C41134F53D} C# classic MSBuild WinExe v4.7 AnyCPU All_Serv_Install All_Serv_Install Program.Main none none System assemblies bin/Debug or bin/Release none only solution project yes no yes no primary corporate application installer/synchronizer ACTIVE_PRIMARY
3 All_Serv_InstallUpdater All_Serv_InstallUpdater/All_Serv_InstallUpdater.csproj {6E075C69-20B5-4613-8BE1-5C41A615C70F} C# classic MSBuild WinExe v4.7 AnyCPU All_Serv_InstallUpdater All_Serv_InstallUpdater Program.Main none none System assemblies bin/Debug or bin/Release none only solution project yes no yes no self-updater for All_Serv_Install ACTIVE_UPDATER
4 All_Serv_Load All_Serv_Load/All_Serv_Load/All_Serv_Load.csproj {CCEF88B4-9157-4F50-AA7F-E0A6F3A665E3} C# classic MSBuild WinExe v4.8 AnyCPU All_Serv_Load All_Serv_Load Program.Main none none System assemblies bin/Debug or bin/Release none only solution project yes no yes no earlier standalone loader/install UI UNKNOWN
5 P_Serv_Starter P_Serv_Starter/P_Serv_Starter.csproj {DBA449EE-5DEC-454B-9EF1-E71BF949D880} C# classic MSBuild Exe v4.7 AnyCPU P_Serv_Starter P_Serv_Starter Program.Main none none System assemblies bin/Debug or bin/Release none only solution project yes no no no external Plant bootstrap selected by installer configuration ACTIVE_BOOTSTRAP
@@ -0,0 +1,84 @@
param(
[string]$DeployedRoot = '\\phpsrv03\NBIM\save\Apps\Plant3D\DriveA\P_Rec_Annot',
[string]$SourceRoot = '\\phpsrv03\NBIM\code\CS\P_Rec_Annot\v1.0.4\repo',
[string]$OutputRoot = (Split-Path -Parent $PSCommandPath)
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
function Get-PeArchitecture([string]$Path) {
if ([IO.Path]::GetExtension($Path) -notin '.dll', '.exe') { return $null }
try {
$stream = [IO.File]::Open($Path, [IO.FileMode]::Open, [IO.FileAccess]::Read, [IO.FileShare]::ReadWrite)
try {
$reader = [IO.BinaryReader]::new($stream)
$stream.Position = 0x3c; $offset = $reader.ReadInt32(); $stream.Position = $offset + 4
$machine = $reader.ReadUInt16()
switch ($machine) { 0x14c { 'x86' } 0x8664 { 'x64' } 0x1c4 { 'ARM' } default { 'Unknown' } }
} finally { $stream.Dispose() }
} catch { 'Unreadable' }
}
function Get-AssemblyMetadata([string]$Path) {
$result = [ordered]@{ AssemblyName = $null; AssemblyVersion = $null; TargetFramework = $null }
if ([IO.Path]::GetExtension($Path) -ne '.dll') { return [pscustomobject]$result }
try {
$name = [Reflection.AssemblyName]::GetAssemblyName($Path)
$result.AssemblyName = $name.Name; $result.AssemblyVersion = $name.Version.ToString()
try {
$assembly = [Reflection.Assembly]::ReflectionOnlyLoadFrom($Path)
$attribute = $assembly.GetCustomAttributesData() | Where-Object { $_.AttributeType.FullName -eq 'System.Runtime.Versioning.TargetFrameworkAttribute' } | Select-Object -First 1
if ($attribute) { $result.TargetFramework = [string]$attribute.ConstructorArguments[0].Value }
} catch { }
} catch { }
[pscustomobject]$result
}
$inventory = foreach ($file in Get-ChildItem -LiteralPath $DeployedRoot -Recurse -File) {
$hash = Get-FileHash -LiteralPath $file.FullName -Algorithm SHA256
$version = try { [Diagnostics.FileVersionInfo]::GetVersionInfo($file.FullName) } catch { $null }
$meta = Get-AssemblyMetadata $file.FullName
[pscustomobject]@{
RelativePath = $file.FullName.Substring($DeployedRoot.Length).TrimStart('\')
Name = $file.Name; Extension = $file.Extension; Length = $file.Length; LastWriteTimeUtc = $file.LastWriteTimeUtc.ToString('o')
Sha256 = $hash.Hash; FileVersion = $version.FileVersion; ProductVersion = $version.ProductVersion
AssemblyName = $meta.AssemblyName; AssemblyVersion = $meta.AssemblyVersion; TargetFramework = $meta.TargetFramework
PeArchitecture = Get-PeArchitecture $file.FullName
}
}
$inventory | Sort-Object RelativePath | Export-Csv -LiteralPath (Join-Path $OutputRoot 'deployed-files.csv') -NoTypeInformation -Encoding utf8
$settings = [xml](Get-Content -LiteralPath (Join-Path $DeployedRoot 'Settings.xml'))
$sequence = 0
$configuration = foreach ($item in $settings.Main.LoadAssemblies.LoadAssembly) {
$sequence++
$configured = [string]$item.assemblyname; $resolved = Join-Path $DeployedRoot $configured
$entry = $inventory | Where-Object Name -eq $configured | Select-Object -First 1
[pscustomobject]@{
Sequence = $sequence; ConfigFile = 'Settings.xml'; ConfiguredPath = $configured; ResolvedPath = ('<deployed-root>\' + $configured)
Assembly = $entry.AssemblyName; Exists = Test-Path -LiteralPath $resolved; Sha256 = $entry.Sha256
LoadMethod = 'acDoc.Editor.Command("._netload", fullPath)'; Required = $true
FailureBehavior = 'Outer loader catch stops remaining sequence and shows MessageBox'; Notes = 'Path is combined with OrthoLoader assembly directory.'
}
}
$configuration | Export-Csv -LiteralPath (Join-Path $OutputRoot 'configuration-map.csv') -NoTypeInformation -Encoding utf8
$projects = Get-ChildItem -LiteralPath $SourceRoot -Recurse -File -Filter *.csproj | ForEach-Object {
$content = Get-Content -LiteralPath $_.FullName -Raw
$assemblyName = if ($content -match '<AssemblyName>([^<]+)</AssemblyName>') { $Matches[1] } else { [IO.Path]::GetFileNameWithoutExtension($_.Name) }
$framework = if ($content -match '<TargetFrameworkVersion>([^<]+)</TargetFrameworkVersion>') { $Matches[1] } else { $null }
$platform = if ($content -match '<PlatformTarget>([^<]+)</PlatformTarget>') { $Matches[1] } else { $null }
[pscustomobject]@{ AssemblyName = $assemblyName; Csproj = $_.FullName.Substring($SourceRoot.Length).TrimStart('\'); TargetFramework = $framework; PlatformTarget = $platform }
}
$sourceMap = foreach ($dll in $inventory | Where-Object Extension -eq '.dll') {
$matches = @($projects | Where-Object { $_.AssemblyName -eq $dll.AssemblyName -or $_.AssemblyName -eq [IO.Path]::GetFileNameWithoutExtension($dll.Name) })
$releaseCandidate = Join-Path (Join-Path $SourceRoot 'FilesRelease') $dll.Name
$exactReleaseMatch = (Test-Path -LiteralPath $releaseCandidate) -and ((Get-FileHash -LiteralPath $releaseCandidate -Algorithm SHA256).Hash -eq $dll.Sha256)
$status = if ($exactReleaseMatch) { 'EXACT_SOURCE_MATCH' } elseif ($matches.Count -eq 1) { 'LIKELY_SOURCE_MATCH' } elseif ($matches.Count -gt 1) { 'MULTIPLE_CANDIDATES' } elseif ($dll.Name -match '^PnP|^Ac') { 'AUTODESK_DEPENDENCY' } else { 'NO_SOURCE_FOUND' }
$project = $matches | Select-Object -First 1
[pscustomobject]@{ DeployedDll=$dll.Name; DeployedSha256=$dll.Sha256; FileVersion=$dll.FileVersion; AssemblyVersion=$dll.AssemblyVersion; LegacyProject=if($project){[IO.Path]::GetFileNameWithoutExtension($project.Csproj)}else{$null}; LegacyCsproj=if($project){$project.Csproj}else{$null}; LegacyOutputName=if($project){$project.AssemblyName}else{$null}; TargetFramework=if($project){$project.TargetFramework}else{$null}; PlatformTarget=if($project){$project.PlatformTarget}else{$null}; CommandCount=$null; RibbonRole=if($dll.AssemblyName -eq 'OrthoLoader'){'Central ribbon/loader'}else{$null}; ConfigurationSource=if($configuration.ConfiguredPath -contains $dll.Name){'Settings.xml'}else{$null}; MatchStatus=$status }
}
$sourceMap | Sort-Object DeployedDll | Export-Csv -LiteralPath (Join-Path $OutputRoot 'deployed-to-source-map.csv') -NoTypeInformation -Encoding utf8
"GeneratedUtc=$([DateTime]::UtcNow.ToString('o'))`nDeployedRoot=<network root redacted to report context>`nSourceRoot=<network source root redacted to report context>`nFiles=$($inventory.Count)" | Set-Content -LiteralPath (Join-Path $OutputRoot 'evidence\generation.txt') -Encoding utf8
@@ -0,0 +1,19 @@
# Legacy P_Rec_Annot loading audit
Scope: read-only inspection of the deployed network directory and the v1.0.4 source checkout. No legacy DLL was executed, copied, or deployed.
The deployed root has 52 files, including 22 managed DLLs. `Settings.xml` enumerates 20 application modules. The inventory and its SHA-256 values are in `deployed-files.csv`; generated report paths use `<deployed-root>` rather than embedding a new runtime configuration path.
High-confidence conclusion: the central loader is `OrthoLoader.dll`. `OrthoLoader.Ribbon.Initialize()` invokes `OrthoLoader.Initialize()`. The latter reads adjacent `Settings.xml` and calls `acDoc.Editor.Command("._netload", fullPath)` sequentially for the configured modules. The bootstrap that initially loads `OrthoLoader.dll` is not present in either audited directory; it remains unknown. Therefore the audit proves same-directory loading, but cannot prove whether an external launcher first NETLOADed the UNC copy or a local copy.
Source-to-binary mapping is `EXACT_SOURCE_MATCH` for 15 deployed DLLs by SHA-256 against `FilesRelease`. Six newer deployed DLLs are `LIKELY_SOURCE_MATCH` by assembly/project identity and metadata but differ byte-for-byte from the checked-in release output. `InsertElevation.dll` is `MULTIPLE_CANDIDATES` because archival projects share the assembly identity.
## Potential test data
| RelativePath | Type | LikelyPurpose | ContainsProjectData | SafeForTest | Reason |
|---|---|---|---|---|---|
| `Template.dwg` | DWG | legacy command template | Unknown | No | Located beside production deployment; no authorization to open or copy it was established. |
| `test.txt` | text | unknown/empty marker | No content | Not useful | Zero bytes. |
| `Справка*.pdf/docx` | documentation | user instructions | Unknown | Review only | Not a CAD fixture. |
No `Project.xml`, `.dcf`, or explicitly labelled sample/demo Ortho DWG was found in the deployed root.
@@ -0,0 +1,21 @@
"Sequence","ConfigFile","ConfiguredPath","ResolvedPath","Assembly","Exists","Sha256","LoadMethod","Required","FailureBehavior","Notes"
"1","Settings.xml","AnnotateLineNumbers.dll","<deployed-root>\AnnotateLineNumbers.dll","AnnotateLineNumbers","True","799BC277ED98BD870F01824180B5A945B24F016DFD9956A8CCC630AFE80FDFA4","acDoc.Editor.Command(""._netload"", fullPath)","True","Outer loader catch stops remaining sequence and shows MessageBox","Path is combined with OrthoLoader assembly directory."
"2","Settings.xml","AutoAnnotateView.dll","<deployed-root>\AutoAnnotateView.dll","AutoAnnotateView","True","EAE55411EC84AC747A3816EE65CD095648282BE44235351E7C9ED1C188BCB611","acDoc.Editor.Command(""._netload"", fullPath)","True","Outer loader catch stops remaining sequence and shows MessageBox","Path is combined with OrthoLoader assembly directory."
"3","Settings.xml","AutoLinenumberConnections.dll","<deployed-root>\AutoLinenumberConnections.dll","AutoLinenumberConnections","True","0F6B2A759F2A63B9152B480B6968BDA20F42AD1D922F67CA6E6BFD31A0715F35","acDoc.Editor.Command(""._netload"", fullPath)","True","Outer loader catch stops remaining sequence and shows MessageBox","Path is combined with OrthoLoader assembly directory."
"4","Settings.xml","CheckOrthoView.dll","<deployed-root>\CheckOrthoView.dll","CheckOrthoView","True","89DB5E537C2BAEC292CDD8A0A43819119850119D54958E8CC3C43E2136AB24A0","acDoc.Editor.Command(""._netload"", fullPath)","True","Outer loader catch stops remaining sequence and shows MessageBox","Path is combined with OrthoLoader assembly directory."
"5","Settings.xml","EnablerLoader.dll","<deployed-root>\EnablerLoader.dll","EnablerLoader","True","745D625C27F92842F2EFF3C515DB48BB9FC0FE59B6E4559ACD696BCC93FF4C51","acDoc.Editor.Command(""._netload"", fullPath)","True","Outer loader catch stops remaining sequence and shows MessageBox","Path is combined with OrthoLoader assembly directory."
"6","Settings.xml","ExtentsIntersect.dll","<deployed-root>\ExtentsIntersect.dll","ExtentsIntersect","True","59D2776E083D4BE2FBFBE4F43009EAA9CE0E81A1752815B45C510F80654169D9","acDoc.Editor.Command(""._netload"", fullPath)","True","Outer loader catch stops remaining sequence and shows MessageBox","Path is combined with OrthoLoader assembly directory."
"7","Settings.xml","GetComponentGeometry.dll","<deployed-root>\GetComponentGeometry.dll","GetComponentGeometry","True","9EA8C48FEC4D36D32CC6AC88B78C434662C16D4484F86EFAA46ECC309E9F9142","acDoc.Editor.Command(""._netload"", fullPath)","True","Outer loader catch stops remaining sequence and shows MessageBox","Path is combined with OrthoLoader assembly directory."
"8","Settings.xml","GetLinenumberAnnotationText.dll","<deployed-root>\GetLinenumberAnnotationText.dll","GetLinenumberAnnotationText","True","047E721DBE380061D9ABDD92BC5475440E3B7ADB2BD0F544D538BA24B121D30E","acDoc.Editor.Command(""._netload"", fullPath)","True","Outer loader catch stops remaining sequence and shows MessageBox","Path is combined with OrthoLoader assembly directory."
"9","Settings.xml","GetPlantScriptFolderPath.dll","<deployed-root>\GetPlantScriptFolderPath.dll","GetPlantScriptFolderPath","True","5080A167A1DAF6C19BE08F6CAB678D490736B5E07E121A918A2915F0D9D8BF65","acDoc.Editor.Command(""._netload"", fullPath)","True","Outer loader catch stops remaining sequence and shows MessageBox","Path is combined with OrthoLoader assembly directory."
"10","Settings.xml","GetScriptParameters.dll","<deployed-root>\GetScriptParameters.dll","GetScriptParameters","True","7FF7B0D26BE8D89FF4A5A4FA1C6EC17BDA1506BCCB78AFE0E650137CD7BA5E87","acDoc.Editor.Command(""._netload"", fullPath)","True","Outer loader catch stops remaining sequence and shows MessageBox","Path is combined with OrthoLoader assembly directory."
"11","Settings.xml","InsertElevation.dll","<deployed-root>\InsertElevation.dll","InsertElevation","True","B2D8B98F353D3DDF3A75A848E6922DC30478A1E5F98B372F24A049690492CA82","acDoc.Editor.Command(""._netload"", fullPath)","True","Outer loader catch stops remaining sequence and shows MessageBox","Path is combined with OrthoLoader assembly directory."
"12","Settings.xml","LoadedAssemblies.dll","<deployed-root>\LoadedAssemblies.dll","LoadedAssemblies","True","277EB83C587A984DC2F558B8A7C86AA98E195A77AFA6FADD7DB83F2B801C0E4E","acDoc.Editor.Command(""._netload"", fullPath)","True","Outer loader catch stops remaining sequence and shows MessageBox","Path is combined with OrthoLoader assembly directory."
"13","Settings.xml","OrthoAutoAnnotate.dll","<deployed-root>\OrthoAutoAnnotate.dll","OrthoAutoAnnotate","True","2E6BB2EBA0A98AA3550DFA4E661832FC7C3EE3DFF49BFC147BD4DFAD7A8596C6","acDoc.Editor.Command(""._netload"", fullPath)","True","Outer loader catch stops remaining sequence and shows MessageBox","Path is combined with OrthoLoader assembly directory."
"14","Settings.xml","P3DCopyMissingObjectsToOrthoDrawing.dll","<deployed-root>\P3DCopyMissingObjectsToOrthoDrawing.dll","P3DCopyMissingObjectsToOrthoDrawing","True","6C879AFC7F35EC25E5E2FFF9FF0EE6EFEB155670DBFD5D0CDE9A0F629C5727B2","acDoc.Editor.Command(""._netload"", fullPath)","True","Outer loader catch stops remaining sequence and shows MessageBox","Path is combined with OrthoLoader assembly directory."
"15","Settings.xml","P3DNewMa.dll","<deployed-root>\P3DNewMa.dll","P3DNewMa","True","97246EE9B24FD373551EF380C5AC0CA78CF7ED6A92581D5331B54445A2A948F8","acDoc.Editor.Command(""._netload"", fullPath)","True","Outer loader catch stops remaining sequence and shows MessageBox","Path is combined with OrthoLoader assembly directory."
"16","Settings.xml","P3DSetLayerForViewport.dll","<deployed-root>\P3DSetLayerForViewport.dll","P3DSetLayerForViewport","True","A28E58FDE4EBAA26450CA646C5A85F98C1A04AA42C0BFC5413D718243ACBF42B","acDoc.Editor.Command(""._netload"", fullPath)","True","Outer loader catch stops remaining sequence and shows MessageBox","Path is combined with OrthoLoader assembly directory."
"17","Settings.xml","PHPAnnotations.dll","<deployed-root>\PHPAnnotations.dll","PHPAnnotations","True","816A693F1B775C5506043DD0461055F248D3897E96AC8285DAE29C5B71FFCA73","acDoc.Editor.Command(""._netload"", fullPath)","True","Outer loader catch stops remaining sequence and shows MessageBox","Path is combined with OrthoLoader assembly directory."
"18","Settings.xml","SwapAttributesMenuItem.dll","<deployed-root>\SwapAttributesMenuItem.dll","SwapAttributesMenuItem","True","674C865E34F9C877561E03E2077F7F2C6852C3C8B6A0503ACAA7619F3E80A460","acDoc.Editor.Command(""._netload"", fullPath)","True","Outer loader catch stops remaining sequence and shows MessageBox","Path is combined with OrthoLoader assembly directory."
"19","Settings.xml","UpdateOrthoDrawings.dll","<deployed-root>\UpdateOrthoDrawings.dll","UpdateOrthoDrawings","True","1492FFF1F56853DF4A858CD3E926640C1E4920B5E3F5CFCC12F4389855E630BA","acDoc.Editor.Command(""._netload"", fullPath)","True","Outer loader catch stops remaining sequence and shows MessageBox","Path is combined with OrthoLoader assembly directory."
"20","Settings.xml","UpdateOrthoViewBlocks.dll","<deployed-root>\UpdateOrthoViewBlocks.dll","UpdateOrthoViewBlocks","True","5022D4D66D79E7E7D82F9FCB647DF8E1FE658A4694EB7794E363D5EA5517F5A9","acDoc.Editor.Command(""._netload"", fullPath)","True","Outer loader catch stops remaining sequence and shows MessageBox","Path is combined with OrthoLoader assembly directory."
1 Sequence ConfigFile ConfiguredPath ResolvedPath Assembly Exists Sha256 LoadMethod Required FailureBehavior Notes
2 1 Settings.xml AnnotateLineNumbers.dll <deployed-root>\AnnotateLineNumbers.dll AnnotateLineNumbers True 799BC277ED98BD870F01824180B5A945B24F016DFD9956A8CCC630AFE80FDFA4 acDoc.Editor.Command("._netload", fullPath) True Outer loader catch stops remaining sequence and shows MessageBox Path is combined with OrthoLoader assembly directory.
3 2 Settings.xml AutoAnnotateView.dll <deployed-root>\AutoAnnotateView.dll AutoAnnotateView True EAE55411EC84AC747A3816EE65CD095648282BE44235351E7C9ED1C188BCB611 acDoc.Editor.Command("._netload", fullPath) True Outer loader catch stops remaining sequence and shows MessageBox Path is combined with OrthoLoader assembly directory.
4 3 Settings.xml AutoLinenumberConnections.dll <deployed-root>\AutoLinenumberConnections.dll AutoLinenumberConnections True 0F6B2A759F2A63B9152B480B6968BDA20F42AD1D922F67CA6E6BFD31A0715F35 acDoc.Editor.Command("._netload", fullPath) True Outer loader catch stops remaining sequence and shows MessageBox Path is combined with OrthoLoader assembly directory.
5 4 Settings.xml CheckOrthoView.dll <deployed-root>\CheckOrthoView.dll CheckOrthoView True 89DB5E537C2BAEC292CDD8A0A43819119850119D54958E8CC3C43E2136AB24A0 acDoc.Editor.Command("._netload", fullPath) True Outer loader catch stops remaining sequence and shows MessageBox Path is combined with OrthoLoader assembly directory.
6 5 Settings.xml EnablerLoader.dll <deployed-root>\EnablerLoader.dll EnablerLoader True 745D625C27F92842F2EFF3C515DB48BB9FC0FE59B6E4559ACD696BCC93FF4C51 acDoc.Editor.Command("._netload", fullPath) True Outer loader catch stops remaining sequence and shows MessageBox Path is combined with OrthoLoader assembly directory.
7 6 Settings.xml ExtentsIntersect.dll <deployed-root>\ExtentsIntersect.dll ExtentsIntersect True 59D2776E083D4BE2FBFBE4F43009EAA9CE0E81A1752815B45C510F80654169D9 acDoc.Editor.Command("._netload", fullPath) True Outer loader catch stops remaining sequence and shows MessageBox Path is combined with OrthoLoader assembly directory.
8 7 Settings.xml GetComponentGeometry.dll <deployed-root>\GetComponentGeometry.dll GetComponentGeometry True 9EA8C48FEC4D36D32CC6AC88B78C434662C16D4484F86EFAA46ECC309E9F9142 acDoc.Editor.Command("._netload", fullPath) True Outer loader catch stops remaining sequence and shows MessageBox Path is combined with OrthoLoader assembly directory.
9 8 Settings.xml GetLinenumberAnnotationText.dll <deployed-root>\GetLinenumberAnnotationText.dll GetLinenumberAnnotationText True 047E721DBE380061D9ABDD92BC5475440E3B7ADB2BD0F544D538BA24B121D30E acDoc.Editor.Command("._netload", fullPath) True Outer loader catch stops remaining sequence and shows MessageBox Path is combined with OrthoLoader assembly directory.
10 9 Settings.xml GetPlantScriptFolderPath.dll <deployed-root>\GetPlantScriptFolderPath.dll GetPlantScriptFolderPath True 5080A167A1DAF6C19BE08F6CAB678D490736B5E07E121A918A2915F0D9D8BF65 acDoc.Editor.Command("._netload", fullPath) True Outer loader catch stops remaining sequence and shows MessageBox Path is combined with OrthoLoader assembly directory.
11 10 Settings.xml GetScriptParameters.dll <deployed-root>\GetScriptParameters.dll GetScriptParameters True 7FF7B0D26BE8D89FF4A5A4FA1C6EC17BDA1506BCCB78AFE0E650137CD7BA5E87 acDoc.Editor.Command("._netload", fullPath) True Outer loader catch stops remaining sequence and shows MessageBox Path is combined with OrthoLoader assembly directory.
12 11 Settings.xml InsertElevation.dll <deployed-root>\InsertElevation.dll InsertElevation True B2D8B98F353D3DDF3A75A848E6922DC30478A1E5F98B372F24A049690492CA82 acDoc.Editor.Command("._netload", fullPath) True Outer loader catch stops remaining sequence and shows MessageBox Path is combined with OrthoLoader assembly directory.
13 12 Settings.xml LoadedAssemblies.dll <deployed-root>\LoadedAssemblies.dll LoadedAssemblies True 277EB83C587A984DC2F558B8A7C86AA98E195A77AFA6FADD7DB83F2B801C0E4E acDoc.Editor.Command("._netload", fullPath) True Outer loader catch stops remaining sequence and shows MessageBox Path is combined with OrthoLoader assembly directory.
14 13 Settings.xml OrthoAutoAnnotate.dll <deployed-root>\OrthoAutoAnnotate.dll OrthoAutoAnnotate True 2E6BB2EBA0A98AA3550DFA4E661832FC7C3EE3DFF49BFC147BD4DFAD7A8596C6 acDoc.Editor.Command("._netload", fullPath) True Outer loader catch stops remaining sequence and shows MessageBox Path is combined with OrthoLoader assembly directory.
15 14 Settings.xml P3DCopyMissingObjectsToOrthoDrawing.dll <deployed-root>\P3DCopyMissingObjectsToOrthoDrawing.dll P3DCopyMissingObjectsToOrthoDrawing True 6C879AFC7F35EC25E5E2FFF9FF0EE6EFEB155670DBFD5D0CDE9A0F629C5727B2 acDoc.Editor.Command("._netload", fullPath) True Outer loader catch stops remaining sequence and shows MessageBox Path is combined with OrthoLoader assembly directory.
16 15 Settings.xml P3DNewMa.dll <deployed-root>\P3DNewMa.dll P3DNewMa True 97246EE9B24FD373551EF380C5AC0CA78CF7ED6A92581D5331B54445A2A948F8 acDoc.Editor.Command("._netload", fullPath) True Outer loader catch stops remaining sequence and shows MessageBox Path is combined with OrthoLoader assembly directory.
17 16 Settings.xml P3DSetLayerForViewport.dll <deployed-root>\P3DSetLayerForViewport.dll P3DSetLayerForViewport True A28E58FDE4EBAA26450CA646C5A85F98C1A04AA42C0BFC5413D718243ACBF42B acDoc.Editor.Command("._netload", fullPath) True Outer loader catch stops remaining sequence and shows MessageBox Path is combined with OrthoLoader assembly directory.
18 17 Settings.xml PHPAnnotations.dll <deployed-root>\PHPAnnotations.dll PHPAnnotations True 816A693F1B775C5506043DD0461055F248D3897E96AC8285DAE29C5B71FFCA73 acDoc.Editor.Command("._netload", fullPath) True Outer loader catch stops remaining sequence and shows MessageBox Path is combined with OrthoLoader assembly directory.
19 18 Settings.xml SwapAttributesMenuItem.dll <deployed-root>\SwapAttributesMenuItem.dll SwapAttributesMenuItem True 674C865E34F9C877561E03E2077F7F2C6852C3C8B6A0503ACAA7619F3E80A460 acDoc.Editor.Command("._netload", fullPath) True Outer loader catch stops remaining sequence and shows MessageBox Path is combined with OrthoLoader assembly directory.
20 19 Settings.xml UpdateOrthoDrawings.dll <deployed-root>\UpdateOrthoDrawings.dll UpdateOrthoDrawings True 1492FFF1F56853DF4A858CD3E926640C1E4920B5E3F5CFCC12F4389855E630BA acDoc.Editor.Command("._netload", fullPath) True Outer loader catch stops remaining sequence and shows MessageBox Path is combined with OrthoLoader assembly directory.
21 20 Settings.xml UpdateOrthoViewBlocks.dll <deployed-root>\UpdateOrthoViewBlocks.dll UpdateOrthoViewBlocks True 5022D4D66D79E7E7D82F9FCB647DF8E1FE658A4694EB7794E363D5EA5517F5A9 acDoc.Editor.Command("._netload", fullPath) True Outer loader catch stops remaining sequence and shows MessageBox Path is combined with OrthoLoader assembly directory.
@@ -0,0 +1,53 @@
"RelativePath","Name","Extension","Length","LastWriteTimeUtc","Sha256","FileVersion","ProductVersion","AssemblyName","AssemblyVersion","TargetFramework","PeArchitecture"
"AnnotateLineNumbers.dll","AnnotateLineNumbers.dll",".dll","126976","2024-12-13T07:41:34.0000000Z","799BC277ED98BD870F01824180B5A945B24F016DFD9956A8CCC630AFE80FDFA4","1.0.4.1","v1.0.4","AnnotateLineNumbers","1.0.4.0",".NETFramework,Version=v4.7","x64"
"AutoAnnotateView.dll","AutoAnnotateView.dll",".dll","49664","2025-02-10T07:47:43.4844455Z","EAE55411EC84AC747A3816EE65CD095648282BE44235351E7C9ED1C188BCB611","1.0.4.6","v1.0.4","AutoAnnotateView","1.0.4.0",".NETFramework,Version=v4.7","x64"
"AutoElevation.dll","AutoElevation.dll",".dll","28160","2025-01-16T08:13:32.2554341Z","A8766B73B30F8F44213769EB7824F82F73ED418DE0EF49695E00A3EB7FC28885","1.0.4.5","v1.0.4","AutoElevation","1.0.4.0",".NETFramework,Version=v4.7","x64"
"AutoLinenumberConnections.dll","AutoLinenumberConnections.dll",".dll","41472","2024-12-13T07:41:34.0000000Z","0F6B2A759F2A63B9152B480B6968BDA20F42AD1D922F67CA6E6BFD31A0715F35","1.0.4.1","v1.0.4","AutoLinenumberConnections","1.0.4.0",".NETFramework,Version=v4.7","x64"
"b455ee6eaed79d76f084846a60ce3284","b455ee6eaed79d76f084846a60ce3284","","0","2023-12-29T11:13:44.6409171Z","E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855",,,,,,
"CheckOrthoView.dll","CheckOrthoView.dll",".dll","14848","2024-12-13T07:41:34.0000000Z","89DB5E537C2BAEC292CDD8A0A43819119850119D54958E8CC3C43E2136AB24A0","1.0.4.1","v1.0.4","CheckOrthoView","1.0.4.0",".NETFramework,Version=v4.7","x64"
"EnablerLoader.dll","EnablerLoader.dll",".dll","6144","2024-12-13T07:41:34.0000000Z","745D625C27F92842F2EFF3C515DB48BB9FC0FE59B6E4559ACD696BCC93FF4C51","1.0.4.1","v1.0.4","EnablerLoader","1.0.4.0",".NETFramework,Version=v4.7","x64"
"ExtentsIntersect.dll","ExtentsIntersect.dll",".dll","6656","2024-12-13T07:41:34.0000000Z","59D2776E083D4BE2FBFBE4F43009EAA9CE0E81A1752815B45C510F80654169D9","1.0.4.1","v1.0.4","ExtentsIntersect","1.0.4.0",".NETFramework,Version=v4.7","x64"
"GetComponentGeometry.dll","GetComponentGeometry.dll",".dll","10240","2024-12-13T07:41:34.0000000Z","9EA8C48FEC4D36D32CC6AC88B78C434662C16D4484F86EFAA46ECC309E9F9142","1.0.4.1","v1.0.4","GetComponentGeometry","1.0.4.0",".NETFramework,Version=v4.7","x64"
"GetComponentGeometry.xml","GetComponentGeometry.xml",".xml","1239","2025-01-14T11:26:24.5054138Z","70169C3A3AAAFE5A2282E01D5B9B7C99F2F4FF9B7514553434302674ABE46C98",,,,,,
"GetLinenumberAnnotationText.dll","GetLinenumberAnnotationText.dll",".dll","23040","2024-12-13T07:41:34.0000000Z","047E721DBE380061D9ABDD92BC5475440E3B7ADB2BD0F544D538BA24B121D30E","1.0.4.1","v1.0.4","GetLinenumberAnnotationText","1.0.4.0",".NETFramework,Version=v4.7","x64"
"GetPlantScriptFolderPath.dll","GetPlantScriptFolderPath.dll",".dll","4608","2024-12-13T07:41:34.0000000Z","5080A167A1DAF6C19BE08F6CAB678D490736B5E07E121A918A2915F0D9D8BF65","1.0.4.1","v1.0.4","GetPlantScriptFolderPath","1.0.4.0",".NETFramework,Version=v4.7","x64"
"GetScriptParameters.dll","GetScriptParameters.dll",".dll","6656","2024-12-13T07:41:34.0000000Z","7FF7B0D26BE8D89FF4A5A4FA1C6EC17BDA1506BCCB78AFE0E650137CD7BA5E87","1.0.4.1","v1.0.4","GetScriptParameters","1.0.4.0",".NETFramework,Version=v4.7","x64"
"ico\button_BJ.png","button_BJ.png",".png","1235","2024-12-13T07:41:34.0000000Z","414F0A23E917D41A82C697B0E9CDDDF9BFCACD1627310BD5717AEA172418AA51",,,,,,
"ico\button_CAC.png","button_CAC.png",".png","1943","2024-12-13T07:41:34.0000000Z","FA2B8877F8B41C70BA62C737C40374394D6840A53DFE2D0B0F83463C4817C138",,,,,,
"ico\button_PXP-AAV.png","button_PXP-AAV.png",".png","1905","2024-12-13T07:41:34.0000000Z","6AB171963B553B434B5AA4BFBE91993B1782606AAF9B486FA23BB27BC1F0E4AA",,,,,,
"ico\button_PXP-AC.png","button_PXP-AC.png",".png","1751","2024-12-13T07:41:34.0000000Z","60A6BD62EF3E50BDDCD00D9C281F2399E6C9D42E0C2DAA1742273C0DC48FE9C3",,,,,,
"ico\button_PXP-AE.png","button_PXP-AE.png",".png","2001","2024-12-13T07:41:34.0000000Z","F1DCF0E908F5AF38CCD13ABA58B824E5B186430414499C0F623C1D5EEAD8DAD7",,,,,,
"ico\button_PXP-ALC.png","button_PXP-ALC.png",".png","1740","2024-12-13T07:41:34.0000000Z","19DB2449B66B95965155E6E3BC313BD52000FE38C95B0FAD90EF07DF53EA9CC9",,,,,,
"ico\button_PXP-COV.png","button_PXP-COV.png",".png","2007","2024-12-13T07:41:34.0000000Z","529BDD045961C244FBD0DE177B899AF9E7CE0F4506B3463C56D2704B3BF207B5",,,,,,
"ico\button_PXP-DAV.png","button_PXP-DAV.png",".png","1249","2025-01-28T06:29:03.3724433Z","A7932F2ABDAC652EA3A901A221AB3BE2BC80FCB76FB203D44FC63AF3681F51E3",,,,,,
"ico\button_PXP-DJ.png","button_PXP-DJ.png",".png","1470","2024-12-13T07:41:34.0000000Z","127957752DC2B6573CD2FD9A2791914BA22802E07E9B7E23CD3AE1FFF9E4FFC4",,,,,,
"ico\button_PXP-ELN.png","button_PXP-ELN.png",".png","1927","2024-12-13T07:41:34.0000000Z","DF32E6A465D5C196A17F3F96473315590DF2D71995F63D2D241D7599B62C99BB",,,,,,
"ico\button_PXP-ESN.png","button_PXP-ESN.png",".png","1636","2024-12-13T07:41:34.0000000Z","0F089184CFD428DA3620F9C0AD2DCBFC21B31ED34FA301E8D5D45923783C0CC7",,,,,,
"ico\button_PXP-IE.png","button_PXP-IE.png",".png","1885","2024-12-13T07:41:34.0000000Z","F1C6373BFAF82E1908AF371D5F21133D27E0B84AFC706A703530855A2046FE43",,,,,,
"ico\button_PXP-IOD.png","button_PXP-IOD.png",".png","1942","2024-12-13T07:41:34.0000000Z","494E90C899A0BB5BB365B0E1452E92A68359610C5A38A37D09F520A39BDB3899",,,,,,
"ico\button_PXP-LN.png","button_PXP-LN.png",".png","2024","2024-12-13T07:41:34.0000000Z","F3305ED6D266E29FA475D4C8A7DF38D2D2A1776A4CE379B876B6CFA278B2FE5D",,,,,,
"ico\button_PXP-MA.png","button_PXP-MA.png",".png","1907","2024-12-13T07:41:34.0000000Z","9386249121644DCF4B9C70491F561E8B6E5BAAE87EBB2C76A84E46B10A60C8C3",,,,,,
"ico\button_PXP-OA.png","button_PXP-OA.png",".png","1999","2024-12-13T07:41:34.0000000Z","EAA2AD7EE5C6B7F3716AF1D7690C76AE59229AE9B6BA02009F5009CD9307F4E8",,,,,,
"ico\button_PXP-OSI.png","button_PXP-OSI.png",".png","1508","2024-12-13T07:41:34.0000000Z","422D10B6EBDD76085CF9C5985391CCA060464B200D6792D604658AAE47F12CEE",,,,,,
"ico\button_PXP-RA.png","button_PXP-RA.png",".png","1826","2024-12-13T07:41:34.0000000Z","1DB3F39C6CCA4EABE79D81DE6DCF9900649CDC9064F2EBF5E45F9AD849B8317F",,,,,,
"ico\button_PXP-RMP.png","button_PXP-RMP.png",".png","1759","2024-12-13T07:41:34.0000000Z","156026EC6DEB7BDBE720047409E9A25615D650D8634C2F0BAF49A71E27135258",,,,,,
"ico\button_PXP-ULN.png","button_PXP-ULN.png",".png","1928","2024-12-13T07:41:34.0000000Z","B37EBB460691EAA7BC3A91DF05C27FE7CC1F0E9F9D69BDA7703956EAA7AA785C",,,,,,
"ico\button_PXP-UOB.png","button_PXP-UOB.png",".png","1569","2024-12-13T07:41:34.0000000Z","3BC16B9923CCD34CF10F403CD65732B217E255E89539174841E7F30D9DFD2DCD",,,,,,
"InsertElevation.dll","InsertElevation.dll",".dll","24576","2025-01-16T08:13:32.2504470Z","B2D8B98F353D3DDF3A75A848E6922DC30478A1E5F98B372F24A049690492CA82","1.0.4.5","v1.0.4","InsertElevation","1.0.4.0",".NETFramework,Version=v4.7","x64"
"LoadedAssemblies.dll","LoadedAssemblies.dll",".dll","4608","2024-12-13T07:41:34.0000000Z","277EB83C587A984DC2F558B8A7C86AA98E195A77AFA6FADD7DB83F2B801C0E4E","1.0.4.1","v1.0.4","LoadedAssemblies","1.0.4.0",".NETFramework,Version=v4.7","x64"
"OrthoAutoAnnotate.dll","OrthoAutoAnnotate.dll",".dll","108544","2025-02-10T07:45:33.3280516Z","2E6BB2EBA0A98AA3550DFA4E661832FC7C3EE3DFF49BFC147BD4DFAD7A8596C6","1.0.4.6","v1.0.4","OrthoAutoAnnotate","1.0.4.0",".NETFramework,Version=v4.7","x64"
"OrthoLoader.dll","OrthoLoader.dll",".dll","18944","2025-02-24T06:59:01.9094468Z","D418F2CBDD134C0156E2D332BD7F9ED471B9A1E0581FE42EDF98BBE01CB3345D","1.0.4.7","v1.0.4","OrthoLoader","1.0.4.0",".NETFramework,Version=v4.7","x64"
"P3DCopyMissingObjectsToOrthoDrawing.dll","P3DCopyMissingObjectsToOrthoDrawing.dll",".dll","14336","2024-12-13T07:41:34.0000000Z","6C879AFC7F35EC25E5E2FFF9FF0EE6EFEB155670DBFD5D0CDE9A0F629C5727B2","1.0.4.1","v1.0.4","P3DCopyMissingObjectsToOrthoDrawing","1.0.4.0",".NETFramework,Version=v4.7","x64"
"P3DNewMa.dll","P3DNewMa.dll",".dll","103936","2025-02-24T06:59:03.1350320Z","97246EE9B24FD373551EF380C5AC0CA78CF7ED6A92581D5331B54445A2A948F8","1.0.4.7","v1.0.4","P3DNewMa","1.0.4.0",".NETFramework,Version=v4.7","x64"
"P3DSetLayerForViewport.dll","P3DSetLayerForViewport.dll",".dll","4608","2024-12-13T07:41:34.0000000Z","A28E58FDE4EBAA26450CA646C5A85F98C1A04AA42C0BFC5413D718243ACBF42B","1.0.4.1","v1.0.4","P3DSetLayerForViewport","1.0.4.0",".NETFramework,Version=v4.7","x64"
"PHPAnnotations.dll","PHPAnnotations.dll",".dll","69120","2024-12-13T07:41:34.0000000Z","816A693F1B775C5506043DD0461055F248D3897E96AC8285DAE29C5B71FFCA73","1.0.4.1","v1.0.4","PHPAnnotations","1.0.4.0",".NETFramework,Version=v4.7","x64"
"Settings.xml","Settings.xml",".xml","136576","2025-01-31T07:28:24.1891507Z","AFEDC7526923ACE1DC60374F98F171809B36992262A37E1F11828F2B8D9799CB",,,,,,
"SwapAttributesMenuItem.dll","SwapAttributesMenuItem.dll",".dll","11264","2024-12-13T07:41:34.0000000Z","674C865E34F9C877561E03E2077F7F2C6852C3C8B6A0503ACAA7619F3E80A460","1.0.4.1","v1.0.4","SwapAttributesMenuItem","1.0.4.0",".NETFramework,Version=v4.7","x64"
"Template.dwg","Template.dwg",".dwg","148438","2025-01-30T13:16:13.0000000Z","EA279A30F113660CF53585451083BAEB0A01CA58C8F9A767C4B7C2A5E66ECCA3",,,,,,
"test.txt","test.txt",".txt","0","2025-01-16T09:56:52.0597137Z","E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855",,,,,,
"UpdateOrthoDrawings.dll","UpdateOrthoDrawings.dll",".dll","45568","2025-02-24T06:59:02.4829428Z","1492FFF1F56853DF4A858CD3E926640C1E4920B5E3F5CFCC12F4389855E630BA","1.0.4.2","v1.0.4","UpdateOrthoDrawings","1.0.4.0",".NETFramework,Version=v4.7","x64"
"UpdateOrthoViewBlocks.dll","UpdateOrthoViewBlocks.dll",".dll","15360","2024-12-13T07:41:34.0000000Z","5022D4D66D79E7E7D82F9FCB647DF8E1FE658A4694EB7794E363D5EA5517F5A9","1.0.4.1","v1.0.4","UpdateOrthoViewBlocks","1.0.4.0",".NETFramework,Version=v4.7","x64"
"Справка по работе с палитрой.docx","Справка по работе с палитрой.docx",".docx","855301","2025-02-05T09:53:08.1186958Z","B6DC65BA17C4EA79DBCCC501909BEDBF56EB6A9F1E24BFAD1B01158B6EC639D0",,,,,,
"Справка по работе с палитрой.pdf","Справка по работе с палитрой.pdf",".pdf","744035","2025-02-05T09:53:21.3752687Z","332CA70D3264C28BC712CA790157D0CD03BE7E07F42603DAF9247600271B7B75",,,,,,
"Справка.docx","Справка.docx",".docx","87889","2025-02-06T05:13:36.4991443Z","14BA9BD252FB6DF37C4D151787E8799AB1703847C1DEC24F3EFE4D6805B0EB12",,,,,,
"Справка.pdf","Справка.pdf",".pdf","154074","2025-02-06T05:14:24.2809131Z","EB840514755A9F56ABC8351B8EC41233FE2512118DB2D4BA0ACD46E74D2C7DC9",,,,,,
1 RelativePath Name Extension Length LastWriteTimeUtc Sha256 FileVersion ProductVersion AssemblyName AssemblyVersion TargetFramework PeArchitecture
2 AnnotateLineNumbers.dll AnnotateLineNumbers.dll .dll 126976 2024-12-13T07:41:34.0000000Z 799BC277ED98BD870F01824180B5A945B24F016DFD9956A8CCC630AFE80FDFA4 1.0.4.1 v1.0.4 AnnotateLineNumbers 1.0.4.0 .NETFramework,Version=v4.7 x64
3 AutoAnnotateView.dll AutoAnnotateView.dll .dll 49664 2025-02-10T07:47:43.4844455Z EAE55411EC84AC747A3816EE65CD095648282BE44235351E7C9ED1C188BCB611 1.0.4.6 v1.0.4 AutoAnnotateView 1.0.4.0 .NETFramework,Version=v4.7 x64
4 AutoElevation.dll AutoElevation.dll .dll 28160 2025-01-16T08:13:32.2554341Z A8766B73B30F8F44213769EB7824F82F73ED418DE0EF49695E00A3EB7FC28885 1.0.4.5 v1.0.4 AutoElevation 1.0.4.0 .NETFramework,Version=v4.7 x64
5 AutoLinenumberConnections.dll AutoLinenumberConnections.dll .dll 41472 2024-12-13T07:41:34.0000000Z 0F6B2A759F2A63B9152B480B6968BDA20F42AD1D922F67CA6E6BFD31A0715F35 1.0.4.1 v1.0.4 AutoLinenumberConnections 1.0.4.0 .NETFramework,Version=v4.7 x64
6 b455ee6eaed79d76f084846a60ce3284 b455ee6eaed79d76f084846a60ce3284 0 2023-12-29T11:13:44.6409171Z E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855
7 CheckOrthoView.dll CheckOrthoView.dll .dll 14848 2024-12-13T07:41:34.0000000Z 89DB5E537C2BAEC292CDD8A0A43819119850119D54958E8CC3C43E2136AB24A0 1.0.4.1 v1.0.4 CheckOrthoView 1.0.4.0 .NETFramework,Version=v4.7 x64
8 EnablerLoader.dll EnablerLoader.dll .dll 6144 2024-12-13T07:41:34.0000000Z 745D625C27F92842F2EFF3C515DB48BB9FC0FE59B6E4559ACD696BCC93FF4C51 1.0.4.1 v1.0.4 EnablerLoader 1.0.4.0 .NETFramework,Version=v4.7 x64
9 ExtentsIntersect.dll ExtentsIntersect.dll .dll 6656 2024-12-13T07:41:34.0000000Z 59D2776E083D4BE2FBFBE4F43009EAA9CE0E81A1752815B45C510F80654169D9 1.0.4.1 v1.0.4 ExtentsIntersect 1.0.4.0 .NETFramework,Version=v4.7 x64
10 GetComponentGeometry.dll GetComponentGeometry.dll .dll 10240 2024-12-13T07:41:34.0000000Z 9EA8C48FEC4D36D32CC6AC88B78C434662C16D4484F86EFAA46ECC309E9F9142 1.0.4.1 v1.0.4 GetComponentGeometry 1.0.4.0 .NETFramework,Version=v4.7 x64
11 GetComponentGeometry.xml GetComponentGeometry.xml .xml 1239 2025-01-14T11:26:24.5054138Z 70169C3A3AAAFE5A2282E01D5B9B7C99F2F4FF9B7514553434302674ABE46C98
12 GetLinenumberAnnotationText.dll GetLinenumberAnnotationText.dll .dll 23040 2024-12-13T07:41:34.0000000Z 047E721DBE380061D9ABDD92BC5475440E3B7ADB2BD0F544D538BA24B121D30E 1.0.4.1 v1.0.4 GetLinenumberAnnotationText 1.0.4.0 .NETFramework,Version=v4.7 x64
13 GetPlantScriptFolderPath.dll GetPlantScriptFolderPath.dll .dll 4608 2024-12-13T07:41:34.0000000Z 5080A167A1DAF6C19BE08F6CAB678D490736B5E07E121A918A2915F0D9D8BF65 1.0.4.1 v1.0.4 GetPlantScriptFolderPath 1.0.4.0 .NETFramework,Version=v4.7 x64
14 GetScriptParameters.dll GetScriptParameters.dll .dll 6656 2024-12-13T07:41:34.0000000Z 7FF7B0D26BE8D89FF4A5A4FA1C6EC17BDA1506BCCB78AFE0E650137CD7BA5E87 1.0.4.1 v1.0.4 GetScriptParameters 1.0.4.0 .NETFramework,Version=v4.7 x64
15 ico\button_BJ.png button_BJ.png .png 1235 2024-12-13T07:41:34.0000000Z 414F0A23E917D41A82C697B0E9CDDDF9BFCACD1627310BD5717AEA172418AA51
16 ico\button_CAC.png button_CAC.png .png 1943 2024-12-13T07:41:34.0000000Z FA2B8877F8B41C70BA62C737C40374394D6840A53DFE2D0B0F83463C4817C138
17 ico\button_PXP-AAV.png button_PXP-AAV.png .png 1905 2024-12-13T07:41:34.0000000Z 6AB171963B553B434B5AA4BFBE91993B1782606AAF9B486FA23BB27BC1F0E4AA
18 ico\button_PXP-AC.png button_PXP-AC.png .png 1751 2024-12-13T07:41:34.0000000Z 60A6BD62EF3E50BDDCD00D9C281F2399E6C9D42E0C2DAA1742273C0DC48FE9C3
19 ico\button_PXP-AE.png button_PXP-AE.png .png 2001 2024-12-13T07:41:34.0000000Z F1DCF0E908F5AF38CCD13ABA58B824E5B186430414499C0F623C1D5EEAD8DAD7
20 ico\button_PXP-ALC.png button_PXP-ALC.png .png 1740 2024-12-13T07:41:34.0000000Z 19DB2449B66B95965155E6E3BC313BD52000FE38C95B0FAD90EF07DF53EA9CC9
21 ico\button_PXP-COV.png button_PXP-COV.png .png 2007 2024-12-13T07:41:34.0000000Z 529BDD045961C244FBD0DE177B899AF9E7CE0F4506B3463C56D2704B3BF207B5
22 ico\button_PXP-DAV.png button_PXP-DAV.png .png 1249 2025-01-28T06:29:03.3724433Z A7932F2ABDAC652EA3A901A221AB3BE2BC80FCB76FB203D44FC63AF3681F51E3
23 ico\button_PXP-DJ.png button_PXP-DJ.png .png 1470 2024-12-13T07:41:34.0000000Z 127957752DC2B6573CD2FD9A2791914BA22802E07E9B7E23CD3AE1FFF9E4FFC4
24 ico\button_PXP-ELN.png button_PXP-ELN.png .png 1927 2024-12-13T07:41:34.0000000Z DF32E6A465D5C196A17F3F96473315590DF2D71995F63D2D241D7599B62C99BB
25 ico\button_PXP-ESN.png button_PXP-ESN.png .png 1636 2024-12-13T07:41:34.0000000Z 0F089184CFD428DA3620F9C0AD2DCBFC21B31ED34FA301E8D5D45923783C0CC7
26 ico\button_PXP-IE.png button_PXP-IE.png .png 1885 2024-12-13T07:41:34.0000000Z F1C6373BFAF82E1908AF371D5F21133D27E0B84AFC706A703530855A2046FE43
27 ico\button_PXP-IOD.png button_PXP-IOD.png .png 1942 2024-12-13T07:41:34.0000000Z 494E90C899A0BB5BB365B0E1452E92A68359610C5A38A37D09F520A39BDB3899
28 ico\button_PXP-LN.png button_PXP-LN.png .png 2024 2024-12-13T07:41:34.0000000Z F3305ED6D266E29FA475D4C8A7DF38D2D2A1776A4CE379B876B6CFA278B2FE5D
29 ico\button_PXP-MA.png button_PXP-MA.png .png 1907 2024-12-13T07:41:34.0000000Z 9386249121644DCF4B9C70491F561E8B6E5BAAE87EBB2C76A84E46B10A60C8C3
30 ico\button_PXP-OA.png button_PXP-OA.png .png 1999 2024-12-13T07:41:34.0000000Z EAA2AD7EE5C6B7F3716AF1D7690C76AE59229AE9B6BA02009F5009CD9307F4E8
31 ico\button_PXP-OSI.png button_PXP-OSI.png .png 1508 2024-12-13T07:41:34.0000000Z 422D10B6EBDD76085CF9C5985391CCA060464B200D6792D604658AAE47F12CEE
32 ico\button_PXP-RA.png button_PXP-RA.png .png 1826 2024-12-13T07:41:34.0000000Z 1DB3F39C6CCA4EABE79D81DE6DCF9900649CDC9064F2EBF5E45F9AD849B8317F
33 ico\button_PXP-RMP.png button_PXP-RMP.png .png 1759 2024-12-13T07:41:34.0000000Z 156026EC6DEB7BDBE720047409E9A25615D650D8634C2F0BAF49A71E27135258
34 ico\button_PXP-ULN.png button_PXP-ULN.png .png 1928 2024-12-13T07:41:34.0000000Z B37EBB460691EAA7BC3A91DF05C27FE7CC1F0E9F9D69BDA7703956EAA7AA785C
35 ico\button_PXP-UOB.png button_PXP-UOB.png .png 1569 2024-12-13T07:41:34.0000000Z 3BC16B9923CCD34CF10F403CD65732B217E255E89539174841E7F30D9DFD2DCD
36 InsertElevation.dll InsertElevation.dll .dll 24576 2025-01-16T08:13:32.2504470Z B2D8B98F353D3DDF3A75A848E6922DC30478A1E5F98B372F24A049690492CA82 1.0.4.5 v1.0.4 InsertElevation 1.0.4.0 .NETFramework,Version=v4.7 x64
37 LoadedAssemblies.dll LoadedAssemblies.dll .dll 4608 2024-12-13T07:41:34.0000000Z 277EB83C587A984DC2F558B8A7C86AA98E195A77AFA6FADD7DB83F2B801C0E4E 1.0.4.1 v1.0.4 LoadedAssemblies 1.0.4.0 .NETFramework,Version=v4.7 x64
38 OrthoAutoAnnotate.dll OrthoAutoAnnotate.dll .dll 108544 2025-02-10T07:45:33.3280516Z 2E6BB2EBA0A98AA3550DFA4E661832FC7C3EE3DFF49BFC147BD4DFAD7A8596C6 1.0.4.6 v1.0.4 OrthoAutoAnnotate 1.0.4.0 .NETFramework,Version=v4.7 x64
39 OrthoLoader.dll OrthoLoader.dll .dll 18944 2025-02-24T06:59:01.9094468Z D418F2CBDD134C0156E2D332BD7F9ED471B9A1E0581FE42EDF98BBE01CB3345D 1.0.4.7 v1.0.4 OrthoLoader 1.0.4.0 .NETFramework,Version=v4.7 x64
40 P3DCopyMissingObjectsToOrthoDrawing.dll P3DCopyMissingObjectsToOrthoDrawing.dll .dll 14336 2024-12-13T07:41:34.0000000Z 6C879AFC7F35EC25E5E2FFF9FF0EE6EFEB155670DBFD5D0CDE9A0F629C5727B2 1.0.4.1 v1.0.4 P3DCopyMissingObjectsToOrthoDrawing 1.0.4.0 .NETFramework,Version=v4.7 x64
41 P3DNewMa.dll P3DNewMa.dll .dll 103936 2025-02-24T06:59:03.1350320Z 97246EE9B24FD373551EF380C5AC0CA78CF7ED6A92581D5331B54445A2A948F8 1.0.4.7 v1.0.4 P3DNewMa 1.0.4.0 .NETFramework,Version=v4.7 x64
42 P3DSetLayerForViewport.dll P3DSetLayerForViewport.dll .dll 4608 2024-12-13T07:41:34.0000000Z A28E58FDE4EBAA26450CA646C5A85F98C1A04AA42C0BFC5413D718243ACBF42B 1.0.4.1 v1.0.4 P3DSetLayerForViewport 1.0.4.0 .NETFramework,Version=v4.7 x64
43 PHPAnnotations.dll PHPAnnotations.dll .dll 69120 2024-12-13T07:41:34.0000000Z 816A693F1B775C5506043DD0461055F248D3897E96AC8285DAE29C5B71FFCA73 1.0.4.1 v1.0.4 PHPAnnotations 1.0.4.0 .NETFramework,Version=v4.7 x64
44 Settings.xml Settings.xml .xml 136576 2025-01-31T07:28:24.1891507Z AFEDC7526923ACE1DC60374F98F171809B36992262A37E1F11828F2B8D9799CB
45 SwapAttributesMenuItem.dll SwapAttributesMenuItem.dll .dll 11264 2024-12-13T07:41:34.0000000Z 674C865E34F9C877561E03E2077F7F2C6852C3C8B6A0503ACAA7619F3E80A460 1.0.4.1 v1.0.4 SwapAttributesMenuItem 1.0.4.0 .NETFramework,Version=v4.7 x64
46 Template.dwg Template.dwg .dwg 148438 2025-01-30T13:16:13.0000000Z EA279A30F113660CF53585451083BAEB0A01CA58C8F9A767C4B7C2A5E66ECCA3
47 test.txt test.txt .txt 0 2025-01-16T09:56:52.0597137Z E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855
48 UpdateOrthoDrawings.dll UpdateOrthoDrawings.dll .dll 45568 2025-02-24T06:59:02.4829428Z 1492FFF1F56853DF4A858CD3E926640C1E4920B5E3F5CFCC12F4389855E630BA 1.0.4.2 v1.0.4 UpdateOrthoDrawings 1.0.4.0 .NETFramework,Version=v4.7 x64
49 UpdateOrthoViewBlocks.dll UpdateOrthoViewBlocks.dll .dll 15360 2024-12-13T07:41:34.0000000Z 5022D4D66D79E7E7D82F9FCB647DF8E1FE658A4694EB7794E363D5EA5517F5A9 1.0.4.1 v1.0.4 UpdateOrthoViewBlocks 1.0.4.0 .NETFramework,Version=v4.7 x64
50 Справка по работе с палитрой.docx Справка по работе с палитрой.docx .docx 855301 2025-02-05T09:53:08.1186958Z B6DC65BA17C4EA79DBCCC501909BEDBF56EB6A9F1E24BFAD1B01158B6EC639D0
51 Справка по работе с палитрой.pdf Справка по работе с палитрой.pdf .pdf 744035 2025-02-05T09:53:21.3752687Z 332CA70D3264C28BC712CA790157D0CD03BE7E07F42603DAF9247600271B7B75
52 Справка.docx Справка.docx .docx 87889 2025-02-06T05:13:36.4991443Z 14BA9BD252FB6DF37C4D151787E8799AB1703847C1DEC24F3EFE4D6805B0EB12
53 Справка.pdf Справка.pdf .pdf 154074 2025-02-06T05:14:24.2809131Z EB840514755A9F56ABC8351B8EC41233FE2512118DB2D4BA0ACD46E74D2C7DC9
@@ -0,0 +1,23 @@
"DeployedDll","DeployedSha256","FileVersion","AssemblyVersion","LegacyProject","LegacyCsproj","LegacyOutputName","TargetFramework","PlatformTarget","CommandCount","RibbonRole","ConfigurationSource","MatchStatus"
"AnnotateLineNumbers.dll","799BC277ED98BD870F01824180B5A945B24F016DFD9956A8CCC630AFE80FDFA4","1.0.4.1","1.0.4.0","AnnotateLineNumbers","AnnotateLineNumbers\AnnotateLineNumbers.csproj","AnnotateLineNumbers","v4.7","x64",,,"Settings.xml","EXACT_SOURCE_MATCH"
"AutoAnnotateView.dll","EAE55411EC84AC747A3816EE65CD095648282BE44235351E7C9ED1C188BCB611","1.0.4.6","1.0.4.0","AutoAnnotateView","AutoAnnotateView\AutoAnnotateView.csproj","AutoAnnotateView","v4.7","x64",,,"Settings.xml","LIKELY_SOURCE_MATCH"
"AutoElevation.dll","A8766B73B30F8F44213769EB7824F82F73ED418DE0EF49695E00A3EB7FC28885","1.0.4.5","1.0.4.0","AutoElevation","AutoElevation\AutoElevation.csproj","AutoElevation","v4.7","x64",,,,"LIKELY_SOURCE_MATCH"
"AutoLinenumberConnections.dll","0F6B2A759F2A63B9152B480B6968BDA20F42AD1D922F67CA6E6BFD31A0715F35","1.0.4.1","1.0.4.0","AutoLinenumberConnections","AutoLinenumberConnections\AutoLinenumberConnections.csproj","AutoLinenumberConnections","v4.7","x64",,,"Settings.xml","EXACT_SOURCE_MATCH"
"CheckOrthoView.dll","89DB5E537C2BAEC292CDD8A0A43819119850119D54958E8CC3C43E2136AB24A0","1.0.4.1","1.0.4.0","CheckOrthoView","CheckOrthoView\CheckOrthoView.csproj","CheckOrthoView","v4.7","x64",,,"Settings.xml","EXACT_SOURCE_MATCH"
"EnablerLoader.dll","745D625C27F92842F2EFF3C515DB48BB9FC0FE59B6E4559ACD696BCC93FF4C51","1.0.4.1","1.0.4.0","EnablerLoader","EnablerLoader\EnablerLoader.csproj","EnablerLoader","v4.7","x64",,,"Settings.xml","EXACT_SOURCE_MATCH"
"ExtentsIntersect.dll","59D2776E083D4BE2FBFBE4F43009EAA9CE0E81A1752815B45C510F80654169D9","1.0.4.1","1.0.4.0","ExtentsIntersect","ExtentsIntersect\ExtentsIntersect.csproj","ExtentsIntersect","v4.7","x64",,,"Settings.xml","EXACT_SOURCE_MATCH"
"GetComponentGeometry.dll","9EA8C48FEC4D36D32CC6AC88B78C434662C16D4484F86EFAA46ECC309E9F9142","1.0.4.1","1.0.4.0","GetComponentGeometry","GetComponentGeometry\GetComponentGeometry.csproj","GetComponentGeometry","v4.7","x64",,,"Settings.xml","EXACT_SOURCE_MATCH"
"GetLinenumberAnnotationText.dll","047E721DBE380061D9ABDD92BC5475440E3B7ADB2BD0F544D538BA24B121D30E","1.0.4.1","1.0.4.0","GetLinenumberAnnotationText","GetLinenumberAnnotationText\GetLinenumberAnnotationText.csproj","GetLinenumberAnnotationText","v4.7","x64",,,"Settings.xml","EXACT_SOURCE_MATCH"
"GetPlantScriptFolderPath.dll","5080A167A1DAF6C19BE08F6CAB678D490736B5E07E121A918A2915F0D9D8BF65","1.0.4.1","1.0.4.0","GetPlantScriptFolderPath","GetPlantScriptFolderPath\GetPlantScriptFolderPath.csproj","GetPlantScriptFolderPath","v4.7","x64",,,"Settings.xml","EXACT_SOURCE_MATCH"
"GetScriptParameters.dll","7FF7B0D26BE8D89FF4A5A4FA1C6EC17BDA1506BCCB78AFE0E650137CD7BA5E87","1.0.4.1","1.0.4.0","GetScriptParameters","GetScriptParameters\GetScriptParameters.csproj","GetScriptParameters","v4.7","x64",,,"Settings.xml","EXACT_SOURCE_MATCH"
"InsertElevation.dll","B2D8B98F353D3DDF3A75A848E6922DC30478A1E5F98B372F24A049690492CA82","1.0.4.5","1.0.4.0","InsertElevation","InsertElevation\InsertElevation.csproj","InsertElevation","v4.7","x64",,,"Settings.xml","MULTIPLE_CANDIDATES"
"LoadedAssemblies.dll","277EB83C587A984DC2F558B8A7C86AA98E195A77AFA6FADD7DB83F2B801C0E4E","1.0.4.1","1.0.4.0","LoadedAssemblies","LoadedAssemblies\LoadedAssemblies.csproj","LoadedAssemblies","v4.7","x64",,,"Settings.xml","EXACT_SOURCE_MATCH"
"OrthoAutoAnnotate.dll","2E6BB2EBA0A98AA3550DFA4E661832FC7C3EE3DFF49BFC147BD4DFAD7A8596C6","1.0.4.6","1.0.4.0","OrthoAutoAnnotate","OrthoAutoAnnotate\OrthoAutoAnnotate.csproj","OrthoAutoAnnotate","v4.7","x64",,,"Settings.xml","LIKELY_SOURCE_MATCH"
"OrthoLoader.dll","D418F2CBDD134C0156E2D332BD7F9ED471B9A1E0581FE42EDF98BBE01CB3345D","1.0.4.7","1.0.4.0","OrthoLoader","OrthoLoader\OrthoLoader.csproj","OrthoLoader","v4.7","x64",,"Central ribbon/loader",,"LIKELY_SOURCE_MATCH"
"P3DCopyMissingObjectsToOrthoDrawing.dll","6C879AFC7F35EC25E5E2FFF9FF0EE6EFEB155670DBFD5D0CDE9A0F629C5727B2","1.0.4.1","1.0.4.0","P3DCopyMissingObjectsToOrthoDrawing","P3DCopyMissingObjectsToOrthoDrawing\P3DCopyMissingObjectsToOrthoDrawing.csproj","P3DCopyMissingObjectsToOrthoDrawing","v4.7","x64",,,"Settings.xml","EXACT_SOURCE_MATCH"
"P3DNewMa.dll","97246EE9B24FD373551EF380C5AC0CA78CF7ED6A92581D5331B54445A2A948F8","1.0.4.7","1.0.4.0","P3DNewMa","P3DNewMa\P3DNewMa.csproj","P3DNewMa","v4.7","x64",,,"Settings.xml","LIKELY_SOURCE_MATCH"
"P3DSetLayerForViewport.dll","A28E58FDE4EBAA26450CA646C5A85F98C1A04AA42C0BFC5413D718243ACBF42B","1.0.4.1","1.0.4.0","P3DSetLayerForViewport","P3DSetLayerForViewport\P3DSetLayerForViewport.csproj","P3DSetLayerForViewport","v4.7","x64",,,"Settings.xml","EXACT_SOURCE_MATCH"
"PHPAnnotations.dll","816A693F1B775C5506043DD0461055F248D3897E96AC8285DAE29C5B71FFCA73","1.0.4.1","1.0.4.0","PHPAnnotations","PHPAnnotations\PHPAnnotations.csproj","PHPAnnotations","v4.7","x64",,,"Settings.xml","EXACT_SOURCE_MATCH"
"SwapAttributesMenuItem.dll","674C865E34F9C877561E03E2077F7F2C6852C3C8B6A0503ACAA7619F3E80A460","1.0.4.1","1.0.4.0","SwapAttributesMenuItem","SwapAttributesMenuItem\SwapAttributesMenuItem.csproj","SwapAttributesMenuItem","v4.7","x64",,,"Settings.xml","EXACT_SOURCE_MATCH"
"UpdateOrthoDrawings.dll","1492FFF1F56853DF4A858CD3E926640C1E4920B5E3F5CFCC12F4389855E630BA","1.0.4.2","1.0.4.0","UpdateOrthoDrawings","UpdateOrthoDrawings\UpdateOrthoDrawings.csproj","UpdateOrthoDrawings","v4.7","x64",,,"Settings.xml","LIKELY_SOURCE_MATCH"
"UpdateOrthoViewBlocks.dll","5022D4D66D79E7E7D82F9FCB647DF8E1FE658A4694EB7794E363D5EA5517F5A9","1.0.4.1","1.0.4.0","UpdateOrthoViewBlocks","UpdateOrthoViewBlocks\UpdateOrthoViewBlocks.csproj","UpdateOrthoViewBlocks","v4.7","x64",,,"Settings.xml","EXACT_SOURCE_MATCH"
1 DeployedDll DeployedSha256 FileVersion AssemblyVersion LegacyProject LegacyCsproj LegacyOutputName TargetFramework PlatformTarget CommandCount RibbonRole ConfigurationSource MatchStatus
2 AnnotateLineNumbers.dll 799BC277ED98BD870F01824180B5A945B24F016DFD9956A8CCC630AFE80FDFA4 1.0.4.1 1.0.4.0 AnnotateLineNumbers AnnotateLineNumbers\AnnotateLineNumbers.csproj AnnotateLineNumbers v4.7 x64 Settings.xml EXACT_SOURCE_MATCH
3 AutoAnnotateView.dll EAE55411EC84AC747A3816EE65CD095648282BE44235351E7C9ED1C188BCB611 1.0.4.6 1.0.4.0 AutoAnnotateView AutoAnnotateView\AutoAnnotateView.csproj AutoAnnotateView v4.7 x64 Settings.xml LIKELY_SOURCE_MATCH
4 AutoElevation.dll A8766B73B30F8F44213769EB7824F82F73ED418DE0EF49695E00A3EB7FC28885 1.0.4.5 1.0.4.0 AutoElevation AutoElevation\AutoElevation.csproj AutoElevation v4.7 x64 LIKELY_SOURCE_MATCH
5 AutoLinenumberConnections.dll 0F6B2A759F2A63B9152B480B6968BDA20F42AD1D922F67CA6E6BFD31A0715F35 1.0.4.1 1.0.4.0 AutoLinenumberConnections AutoLinenumberConnections\AutoLinenumberConnections.csproj AutoLinenumberConnections v4.7 x64 Settings.xml EXACT_SOURCE_MATCH
6 CheckOrthoView.dll 89DB5E537C2BAEC292CDD8A0A43819119850119D54958E8CC3C43E2136AB24A0 1.0.4.1 1.0.4.0 CheckOrthoView CheckOrthoView\CheckOrthoView.csproj CheckOrthoView v4.7 x64 Settings.xml EXACT_SOURCE_MATCH
7 EnablerLoader.dll 745D625C27F92842F2EFF3C515DB48BB9FC0FE59B6E4559ACD696BCC93FF4C51 1.0.4.1 1.0.4.0 EnablerLoader EnablerLoader\EnablerLoader.csproj EnablerLoader v4.7 x64 Settings.xml EXACT_SOURCE_MATCH
8 ExtentsIntersect.dll 59D2776E083D4BE2FBFBE4F43009EAA9CE0E81A1752815B45C510F80654169D9 1.0.4.1 1.0.4.0 ExtentsIntersect ExtentsIntersect\ExtentsIntersect.csproj ExtentsIntersect v4.7 x64 Settings.xml EXACT_SOURCE_MATCH
9 GetComponentGeometry.dll 9EA8C48FEC4D36D32CC6AC88B78C434662C16D4484F86EFAA46ECC309E9F9142 1.0.4.1 1.0.4.0 GetComponentGeometry GetComponentGeometry\GetComponentGeometry.csproj GetComponentGeometry v4.7 x64 Settings.xml EXACT_SOURCE_MATCH
10 GetLinenumberAnnotationText.dll 047E721DBE380061D9ABDD92BC5475440E3B7ADB2BD0F544D538BA24B121D30E 1.0.4.1 1.0.4.0 GetLinenumberAnnotationText GetLinenumberAnnotationText\GetLinenumberAnnotationText.csproj GetLinenumberAnnotationText v4.7 x64 Settings.xml EXACT_SOURCE_MATCH
11 GetPlantScriptFolderPath.dll 5080A167A1DAF6C19BE08F6CAB678D490736B5E07E121A918A2915F0D9D8BF65 1.0.4.1 1.0.4.0 GetPlantScriptFolderPath GetPlantScriptFolderPath\GetPlantScriptFolderPath.csproj GetPlantScriptFolderPath v4.7 x64 Settings.xml EXACT_SOURCE_MATCH
12 GetScriptParameters.dll 7FF7B0D26BE8D89FF4A5A4FA1C6EC17BDA1506BCCB78AFE0E650137CD7BA5E87 1.0.4.1 1.0.4.0 GetScriptParameters GetScriptParameters\GetScriptParameters.csproj GetScriptParameters v4.7 x64 Settings.xml EXACT_SOURCE_MATCH
13 InsertElevation.dll B2D8B98F353D3DDF3A75A848E6922DC30478A1E5F98B372F24A049690492CA82 1.0.4.5 1.0.4.0 InsertElevation InsertElevation\InsertElevation.csproj InsertElevation v4.7 x64 Settings.xml MULTIPLE_CANDIDATES
14 LoadedAssemblies.dll 277EB83C587A984DC2F558B8A7C86AA98E195A77AFA6FADD7DB83F2B801C0E4E 1.0.4.1 1.0.4.0 LoadedAssemblies LoadedAssemblies\LoadedAssemblies.csproj LoadedAssemblies v4.7 x64 Settings.xml EXACT_SOURCE_MATCH
15 OrthoAutoAnnotate.dll 2E6BB2EBA0A98AA3550DFA4E661832FC7C3EE3DFF49BFC147BD4DFAD7A8596C6 1.0.4.6 1.0.4.0 OrthoAutoAnnotate OrthoAutoAnnotate\OrthoAutoAnnotate.csproj OrthoAutoAnnotate v4.7 x64 Settings.xml LIKELY_SOURCE_MATCH
16 OrthoLoader.dll D418F2CBDD134C0156E2D332BD7F9ED471B9A1E0581FE42EDF98BBE01CB3345D 1.0.4.7 1.0.4.0 OrthoLoader OrthoLoader\OrthoLoader.csproj OrthoLoader v4.7 x64 Central ribbon/loader LIKELY_SOURCE_MATCH
17 P3DCopyMissingObjectsToOrthoDrawing.dll 6C879AFC7F35EC25E5E2FFF9FF0EE6EFEB155670DBFD5D0CDE9A0F629C5727B2 1.0.4.1 1.0.4.0 P3DCopyMissingObjectsToOrthoDrawing P3DCopyMissingObjectsToOrthoDrawing\P3DCopyMissingObjectsToOrthoDrawing.csproj P3DCopyMissingObjectsToOrthoDrawing v4.7 x64 Settings.xml EXACT_SOURCE_MATCH
18 P3DNewMa.dll 97246EE9B24FD373551EF380C5AC0CA78CF7ED6A92581D5331B54445A2A948F8 1.0.4.7 1.0.4.0 P3DNewMa P3DNewMa\P3DNewMa.csproj P3DNewMa v4.7 x64 Settings.xml LIKELY_SOURCE_MATCH
19 P3DSetLayerForViewport.dll A28E58FDE4EBAA26450CA646C5A85F98C1A04AA42C0BFC5413D718243ACBF42B 1.0.4.1 1.0.4.0 P3DSetLayerForViewport P3DSetLayerForViewport\P3DSetLayerForViewport.csproj P3DSetLayerForViewport v4.7 x64 Settings.xml EXACT_SOURCE_MATCH
20 PHPAnnotations.dll 816A693F1B775C5506043DD0461055F248D3897E96AC8285DAE29C5B71FFCA73 1.0.4.1 1.0.4.0 PHPAnnotations PHPAnnotations\PHPAnnotations.csproj PHPAnnotations v4.7 x64 Settings.xml EXACT_SOURCE_MATCH
21 SwapAttributesMenuItem.dll 674C865E34F9C877561E03E2077F7F2C6852C3C8B6A0503ACAA7619F3E80A460 1.0.4.1 1.0.4.0 SwapAttributesMenuItem SwapAttributesMenuItem\SwapAttributesMenuItem.csproj SwapAttributesMenuItem v4.7 x64 Settings.xml EXACT_SOURCE_MATCH
22 UpdateOrthoDrawings.dll 1492FFF1F56853DF4A858CD3E926640C1E4920B5E3F5CFCC12F4389855E630BA 1.0.4.2 1.0.4.0 UpdateOrthoDrawings UpdateOrthoDrawings\UpdateOrthoDrawings.csproj UpdateOrthoDrawings v4.7 x64 Settings.xml LIKELY_SOURCE_MATCH
23 UpdateOrthoViewBlocks.dll 5022D4D66D79E7E7D82F9FCB647DF8E1FE658A4694EB7794E363D5EA5517F5A9 1.0.4.1 1.0.4.0 UpdateOrthoViewBlocks UpdateOrthoViewBlocks\UpdateOrthoViewBlocks.csproj UpdateOrthoViewBlocks v4.7 x64 Settings.xml EXACT_SOURCE_MATCH
@@ -0,0 +1,4 @@
GeneratedUtc=2026-07-31T10:39:04.9899963Z
DeployedRoot=<network root redacted to report context>
SourceRoot=<network source root redacted to report context>
Files=52
@@ -0,0 +1,13 @@
# Legacy versus new loading model
| Legacy behavior | Legacy evidence | New implementation | Compatibility requirement | Decision | Risk |
|---|---|---|---|---|---|
| Unknown external bootstrap loads `OrthoLoader.dll` | `loading-chain.md` | Bundle bootstrap not selected yet | Identify the workstation/project startup owner first | Defer loading-model decision | High |
| 20 independent DLLs loaded in order | `Settings.xml`, `Loader.cs:63-81` | one entry assembly currently | Preserve required command availability, not the DLL count | Evaluate command/module migration individually | Medium |
| DLLs loaded from OrthoLoader's directory (possibly UNC) | `Loader.cs:49,65`; bootstrap source unknown | local smoke bundle only | Decide offline/update policy with operations owner | Do not hard-code UNC in new code | High |
| Settings.xml controls order | `Loader.cs:104-112` | MSBuild/bundle manifest currently | Preserve explicit dependency ordering where behavior requires it | Document, do not emulate blindly | Medium |
| central PluginsOrtho Ribbon | `Ribbon.cs` | new Promhimproekt tab | Preserve actual command mapping where adopted | Separate UI migration decision | Medium |
| project selection uses local app_netload.lsp | `Ribbon.cs:380-405` | no equivalent | Confirm security/project-selection policy | Do not reproduce hidden local control file | High |
| network icons and documents | `Ribbon.cs:330-357`, deployed files | bundle resources | Bundle only licensed, necessary resources | Replace network runtime dependency | Medium |
Recommendation: do not choose AppLoader or a manual-NETLOAD model yet. First identify the missing external bootstrap and confirm whether `app_netload.lsp` is the corporate launcher contract. Once confirmed, use a single versioned entry assembly with explicit manifest/dependency validation; avoid sequential programmatic NETLOAD and UNC execution because the legacy chain is non-atomic and fails the remaining modules after one error.
@@ -0,0 +1,29 @@
# Proven legacy loading chain
```text
Unknown external bootstrap
-> NETLOAD of deployed OrthoLoader.dll (not evidenced in audited directories)
-> OrthoLoader.Ribbon.Initialize()
-> subscribe ComponentManager.ItemInitialized
-> OrthoLoader.Initialize()
-> adjacent Settings.xml / Main/LoadAssemblies/LoadAssembly
-> sequential acDoc.Editor.Command("._netload", fullPath) for 20 DLLs
-> each DLL exposes CommandMethod commands
-> Ribbon ItemInitialized creates PluginsOrtho tab and command buttons
```
| Claim | Evidence | Confidence |
|---|---|---|
| Central loader is `OrthoLoader.dll` | `OrthoLoader/Ribbon.cs:151-156`; deployed DLL metadata and `Settings.xml` | High |
| Module list is read from adjacent `Settings.xml` | `OrthoLoader/Loader.cs:104-112` | High |
| Module loading is programmatic `._netload`, in XML order | `OrthoLoader/Loader.cs:63-81`; `configuration-map.csv` | High |
| Full DLL path is loader directory + configured filename | `OrthoLoader/Loader.cs:49, 65` | High |
| Modules load from the same directory as OrthoLoader | `Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)` in `Loader.cs:49,65` | High |
| Modules loaded directly from UNC | true only if external bootstrap NETLOADed the deployed UNC copy; bootstrap is outside audit scope | Unknown |
| Missing/failed module stops later loading and shows MessageBox | single outer try/catch around the loop, `Loader.cs:63-99` | High |
| Ribbon owner is OrthoLoader | `OrthoLoader/Ribbon.cs:16-19,151-172,193-321` | High |
| Initial bootstrap mechanism | no LISP/CUI/ARG/REG/PackageContents file found in either audited directory | Unknown |
`Assembly.LoadFrom` occurs inside several legacy command implementations for secondary dependencies. It is not the mechanism used by `OrthoLoader` to load its configured modules. `EnablerLoader` separately uses `Assembly.LoadFile` for the Plant enabler from the host base directory.
No `PackageContents.xml`, `*.bundle`, registry demand-load registration, LISP, CUI, MNL, script, batch, or PowerShell bootstrap was found in the audited locations. This proves absence only within audit scope; it does not prove the workstation bootstrap did not exist elsewhere.
@@ -0,0 +1,10 @@
Dependency,Category,Evidence,Requiredness,Notes
Settings.xml,CONFIGURATION,OrthoLoader/Loader.cs:104-112,Required,Adjacent to each loaded assembly directory
ico/*.png,RIBBON_RESOURCE,OrthoLoader/Ribbon.cs:330-357,Optional,Missing file yields null image
app_netload.lsp,CONFIGURATION,OrthoLoader/Ribbon.cs:380-405,Implicit,Local workstation file gates project-specific Ribbon enablement
annotation_settings.xml,REFERENCE_DATA,README.md and annotation modules,Project-dependent,Located in Plant project data area according to README
users_indexes_list.xml,REFERENCE_DATA,README.md and annotation modules,Project-dependent,Located in Plant project data area according to README
Template.dwg,TEMPLATE,Deployed inventory and legacy source references,Command-dependent,Do not treat deployed copy as approved test fixture
Plant project XML/DWG,COMMAND_TIME,UpdateOrthoDrawings and annotation source,Required by relevant command,Out of deployed plugin root
Update report CSV,LOGGING,UpdateOrthoDrawings/Commands.cs:1004,Optional,Written beside executing assembly by legacy command
Help PDFs,RIBBON_RESOURCE,P3DNewMa/NewMaControl.cs:6214,Optional,Opened from assembly directory
1 Dependency Category Evidence Requiredness Notes
2 Settings.xml CONFIGURATION OrthoLoader/Loader.cs:104-112 Required Adjacent to each loaded assembly directory
3 ico/*.png RIBBON_RESOURCE OrthoLoader/Ribbon.cs:330-357 Optional Missing file yields null image
4 app_netload.lsp CONFIGURATION OrthoLoader/Ribbon.cs:380-405 Implicit Local workstation file gates project-specific Ribbon enablement
5 annotation_settings.xml REFERENCE_DATA README.md and annotation modules Project-dependent Located in Plant project data area according to README
6 users_indexes_list.xml REFERENCE_DATA README.md and annotation modules Project-dependent Located in Plant project data area according to README
7 Template.dwg TEMPLATE Deployed inventory and legacy source references Command-dependent Do not treat deployed copy as approved test fixture
8 Plant project XML/DWG COMMAND_TIME UpdateOrthoDrawings and annotation source Required by relevant command Out of deployed plugin root
9 Update report CSV LOGGING UpdateOrthoDrawings/Commands.cs:1004 Optional Written beside executing assembly by legacy command
10 Help PDFs RIBBON_RESOURCE P3DNewMa/NewMaControl.cs:6214 Optional Opened from assembly directory
@@ -0,0 +1,9 @@
# Observed user workflow
After an external bootstrap loaded `OrthoLoader.dll`, the loader created a `PluginsOrtho` Ribbon tab only when `isProjectCorrect` accepted the current Plant project. That predicate reads the first line of the local file `C:\AutoCAD User Support\Applications\app_netload.lsp` and compares its encoded project root with the active Plant project (`OrthoLoader/Ribbon.cs:380-405`).
`PluginsOrtho` has eight panels. Button IDs are commands; clicks call `Document.SendStringToExecute(command + "\n", ...)` (`Ribbon.cs:193-321, 526-529`). Examples: `PXP-AAV` is registered by `AutoAnnotateView/Commands.cs:546`; `PXP-OA` by `OrthoAutoAnnotate/Commands.cs:6444`.
The deployed `ico/*.png` files are runtime Ribbon resources. They are resolved from the loader assembly directory; in this deployment that is the UNC directory. The code has a null-image fallback when an icon is absent.
The tab and panels use stable IDs and lookup before creation. Button collections are appended when a missing panel is created; repeated project-open events can enable existing UI or create absent UI. The event is detached after first Ribbon initialization, but `CurrentProjectOpenedEvent` is retained for the process lifetime.
+35
View File
@@ -0,0 +1,35 @@
# Capability ledger
This directory is the versioned, machine-readable register that prevents active
2019 capabilities from silently disappearing during the Autodesk 2026 migration.
It is product-neutral by schema: Plant 3D, Revit and Navisworks records use the
same required fields, while product adapters remain outside the ledger.
Current coverage is deliberately narrow: active, non-archive `P_Rec_Annot`
Plant 3D command registrations. The generated ledger does not claim to be a
complete inventory of the 2019 ecosystem. Launcher, profiles, palettes,
resources, Revit and Navisworks must be added as independently audited slices.
## Working journal — 2026-08-05
1. This work makes a real 2019 capability visible, owned and decidable rather
than treating the first migrated command as the programme boundary.
2. It closes the risk that active P_Rec_Annot commands disappear unnoticed.
3. The schema is reusable for Revit and Navisworks because it describes a
capability and its evidence, not a Plant API.
4. No Launcher or product domain logic is introduced.
5. The scope statement and source pointer make missing coverage explicit.
6. Equivalence is proven only by the record's verification method and runtime
evidence; `NOT REPORTED` remains explicit.
7. Users gain an accountable migration path for every currently-audited active
P_Rec_Annot command, including the already verified reference vertical.
## Regeneration and validation
```powershell
pwsh migration/tools/Build-CapabilityLedger.ps1
pwsh migration/tools/Test-CapabilityLedger.ps1
```
The builder consumes audited source records only, excludes `_archive`, writes
stable IDs, and retains unknowns instead of manufacturing a target decision.
@@ -0,0 +1,607 @@
{
"schemaVersion": 1,
"scope": "Plant 3D P_Rec_Annot active command slice generated from audited non-archive command registrations. This is a ledger foundation, not a claim of complete ecosystem inventory.",
"source": "migration/audits/p-rec-annot-v1.0.4/evidence/commands.csv",
"capabilities": [
{
"id": "PLANT3D-2019-CMD-BJ",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "PHPAnnotations\\PHPBlockJig.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: BJ",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-CAC",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "PHPAnnotations\\PHPAnnotations.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: CAC",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-CAC1",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "P3DNewMa\\NewMaControl.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: CAC1",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-CREATEANNOTATIONCOMMAND",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "PHPAnnotations\\PHPAnnotations.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: CreateAnnotationCommand",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-MTO-MA",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "P3DNewMa\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: mto-ma",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-MTO-MA1",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "P3DNewMa\\NewMaControl.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: mto-ma1",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-PXP-AAV",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "AutoAnnotateView\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-AAV",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "PRecAnnot.Plant3D2026",
"owner": "Plant annotation domain owner",
"migrationStatus": "RUNTIME_VERIFIED",
"verificationMethod": "Controlled Plant 3D 2026 Preview/Apply/repeated Apply/UNDO runtime acceptance",
"runtimeEvidence": "Accepted runtime session 7402fee6290b44438642f9204e8bdb6c; automation journal 0cf74ff1c90d4c69a10417904638c7b6",
"knownDifferences": "2026 implementation uses local immutable package staging and managed entry assembly; legacy loading mechanics are not retained.",
"decision": "MIGRATED_RUNTIME_VERIFIED"
},
{
"id": "PLANT3D-2019-CMD-PXP-AC",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "SwapAttributesMenuItem\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-AC",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-PXP-AE",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "AutoElevation\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-AE",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-PXP-ALC",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "AutoLinenumberConnections\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-ALC",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-PXP-COV",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "CheckOrthoView\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-COV",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-PXP-DJ",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "OrthoAutoAnnotate\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-DJ",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-PXP-ELN",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "AnnotateLineNumbers\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-ELN",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-PXP-ESN",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "AnnotateLineNumbers\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-ESN",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-PXP-GIT",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "ExtentsIntersect\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-GIT",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-PXP-IE",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "InsertElevation\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-IE",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-PXP-IOD",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "UpdateOrthoDrawings\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-IOD",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-PXP-LN",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "AnnotateLineNumbers\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-LN",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-PXP-MA",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "PHPAnnotations\\PHPAnnotations.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-MA",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-PXP-OA",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "OrthoAutoAnnotate\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-OA",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "PRecAnnot.Plant3D2026",
"owner": "Plant annotation domain owner",
"migrationStatus": "RUNTIME_VERIFIED",
"verificationMethod": "Controlled Plant 3D 2026 Preview/Apply/repeated Apply/UNDO runtime acceptance",
"runtimeEvidence": "Accepted runtime session 7402fee6290b44438642f9204e8bdb6c; automation journal 0cf74ff1c90d4c69a10417904638c7b6",
"knownDifferences": "2026 implementation uses local immutable package staging and managed entry assembly; legacy loading mechanics are not retained.",
"decision": "MIGRATED_RUNTIME_VERIFIED"
},
{
"id": "PLANT3D-2019-CMD-PXP-OSI",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "OrthoAutoAnnotate\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-OSI",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-PXP-RA",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "OrthoAutoAnnotate\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-RA",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-PXP-RMP",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "AnnotateLineNumbers\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-RMP",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-PXP-ULN",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "AnnotateLineNumbers\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-ULN",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
},
{
"id": "PLANT3D-2019-CMD-PXP-UOB",
"sourceProduct": "Autodesk Plant 3D 2019",
"sourceComponent": "UpdateOrthoViewBlocks\\Commands.cs",
"userPurpose": "Registered AutoCAD command; command body and workflow require source-specific acceptance.",
"launchMethod": "AutoCAD command: PXP-UOB",
"dependencies": [
"Plant 3D drawing",
"P_Rec_Annot legacy module"
],
"resources": [
"Project configuration",
"Drawing and annotation resources"
],
"projectConstraints": "Plant project: verify body; reads drawing: yes; writes drawing: unknown",
"legacyStatus": "ACTIVE_LEGACY",
"targetComponent": "UNASSIGNED",
"owner": "UNASSIGNED",
"migrationStatus": "MIGRATION_PLANNED",
"verificationMethod": "Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance",
"runtimeEvidence": "NOT REPORTED",
"knownDifferences": "NOT ASSESSED",
"decision": "MIGRATION_REQUIRED"
}
]
}
+35
View File
@@ -0,0 +1,35 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Autodesk 2019 to 2026 capability ledger",
"type": "object",
"required": ["schemaVersion", "scope", "capabilities"],
"properties": {
"schemaVersion": { "const": 1 },
"scope": { "type": "string", "minLength": 1 },
"capabilities": {
"type": "array",
"items": {
"type": "object",
"required": ["id", "sourceProduct", "sourceComponent", "userPurpose", "launchMethod", "dependencies", "resources", "projectConstraints", "legacyStatus", "targetComponent", "owner", "migrationStatus", "verificationMethod", "runtimeEvidence", "knownDifferences", "decision"],
"properties": {
"id": { "type": "string", "pattern": "^[A-Z0-9-]+$" },
"sourceProduct": { "type": "string" },
"sourceComponent": { "type": "string" },
"userPurpose": { "type": "string" },
"launchMethod": { "type": "string" },
"dependencies": { "type": "array", "items": { "type": "string" } },
"resources": { "type": "array", "items": { "type": "string" } },
"projectConstraints": { "type": "string" },
"legacyStatus": { "type": "string" },
"targetComponent": { "type": "string" },
"owner": { "type": "string" },
"migrationStatus": { "type": "string" },
"verificationMethod": { "type": "string" },
"runtimeEvidence": { "type": "string" },
"knownDifferences": { "type": "string" },
"decision": { "type": "string" }
}
}
}
}
}
@@ -0,0 +1,62 @@
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[string]$RepositoryRoot,
[Parameter(Mandatory = $false)]
[string]$OutputPath
)
$ErrorActionPreference = 'Stop'
if (-not $RepositoryRoot) { $RepositoryRoot = Split-Path -Parent $PSScriptRoot }
$RepositoryRoot = [IO.Path]::GetFullPath($RepositoryRoot)
$commandsPath = Join-Path $RepositoryRoot 'audits\p-rec-annot-v1.0.4\evidence\commands.csv'
if (-not (Test-Path -LiteralPath $commandsPath -PathType Leaf)) { throw "CAPABILITY_LEDGER_COMMAND_SOURCE_MISSING: $commandsPath" }
if (-not $OutputPath) { $OutputPath = Join-Path $RepositoryRoot 'capability-ledger\plant3d-p-rec-annot-commands.json' }
$OutputPath = [IO.Path]::GetFullPath($OutputPath)
$accepted = @{
'PXP-AAV' = @{ Component = 'PRecAnnot.Plant3D2026'; Evidence = 'Accepted runtime session 7402fee6290b44438642f9204e8bdb6c; automation journal 0cf74ff1c90d4c69a10417904638c7b6'; Decision = 'MIGRATED_RUNTIME_VERIFIED' }
'PXP-OA' = @{ Component = 'PRecAnnot.Plant3D2026'; Evidence = 'Accepted runtime session 7402fee6290b44438642f9204e8bdb6c; automation journal 0cf74ff1c90d4c69a10417904638c7b6'; Decision = 'MIGRATED_RUNTIME_VERIFIED' }
}
$commands = Import-Csv -LiteralPath $commandsPath | Where-Object { $_.File -notmatch '(^|\\)_archive(\\|$)' } |
Group-Object Command | ForEach-Object { $_.Group | Select-Object -First 1 } | Sort-Object Command
$capabilities = foreach ($command in $commands) {
$acceptedRecord = $accepted[$command.Command]
$status = if ($acceptedRecord) { 'RUNTIME_VERIFIED' } else { 'MIGRATION_PLANNED' }
[ordered]@{
id = ('PLANT3D-2019-CMD-' + ($command.Command.ToUpperInvariant() -replace '[^A-Z0-9]+', '-').Trim('-'))
sourceProduct = 'Autodesk Plant 3D 2019'
sourceComponent = $command.File
userPurpose = 'Registered AutoCAD command; command body and workflow require source-specific acceptance.'
launchMethod = ('AutoCAD command: ' + $command.Command)
dependencies = @('Plant 3D drawing', 'P_Rec_Annot legacy module')
resources = @('Project configuration', 'Drawing and annotation resources')
projectConstraints = ('Plant project: ' + $command.PlantProject + '; reads drawing: ' + $command.ReadsDrawing + '; writes drawing: ' + $command.WritesDrawing)
legacyStatus = 'ACTIVE_LEGACY'
targetComponent = if ($acceptedRecord) { $acceptedRecord.Component } else { 'UNASSIGNED' }
owner = if ($acceptedRecord) { 'Plant annotation domain owner' } else { 'UNASSIGNED' }
migrationStatus = $status
verificationMethod = if ($acceptedRecord) { 'Controlled Plant 3D 2026 Preview/Apply/repeated Apply/UNDO runtime acceptance' } else { 'Legacy source audit, contract, deterministic Preview, controlled Apply, idempotency, UNDO and visual acceptance' }
runtimeEvidence = if ($acceptedRecord) { $acceptedRecord.Evidence } else { 'NOT REPORTED' }
knownDifferences = if ($acceptedRecord) { '2026 implementation uses local immutable package staging and managed entry assembly; legacy loading mechanics are not retained.' } else { 'NOT ASSESSED' }
decision = if ($acceptedRecord) { $acceptedRecord.Decision } else { 'MIGRATION_REQUIRED' }
}
}
$ledger = [ordered]@{
schemaVersion = 1
scope = 'Plant 3D P_Rec_Annot active command slice generated from audited non-archive command registrations. This is a ledger foundation, not a claim of complete ecosystem inventory.'
source = 'migration/audits/p-rec-annot-v1.0.4/evidence/commands.csv'
capabilities = @($capabilities)
}
$parent = Split-Path -Parent $OutputPath
New-Item -ItemType Directory -Path $parent -Force | Out-Null
$temporary = Join-Path $parent ((Split-Path -Leaf $OutputPath) + '.tmp')
$json = $ledger | ConvertTo-Json -Depth 8
[IO.File]::WriteAllText($temporary, $json + [Environment]::NewLine, [Text.UTF8Encoding]::new($false))
Move-Item -LiteralPath $temporary -Destination $OutputPath -Force
Write-Output "CAPABILITY_LEDGER_WRITTEN=$OutputPath"
Write-Output "CAPABILITY_COUNT=$($capabilities.Count)"
+23
View File
@@ -0,0 +1,23 @@
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[string]$RepositoryRoot
)
$ErrorActionPreference = 'Stop'
if (-not $RepositoryRoot) { $RepositoryRoot = Split-Path -Parent $PSScriptRoot }
$RepositoryRoot = [IO.Path]::GetFullPath($RepositoryRoot)
$ledgerPath = Join-Path $RepositoryRoot 'capability-ledger\plant3d-p-rec-annot-commands.json'
$sourcePath = Join-Path $RepositoryRoot 'audits\p-rec-annot-v1.0.4\evidence\commands.csv'
& (Join-Path $PSScriptRoot 'Build-CapabilityLedger.ps1') -RepositoryRoot $RepositoryRoot -OutputPath $ledgerPath | Out-Null
$ledger = Get-Content -LiteralPath $ledgerPath -Raw -Encoding utf8 | ConvertFrom-Json
$required = 'id','sourceProduct','sourceComponent','userPurpose','launchMethod','dependencies','resources','projectConstraints','legacyStatus','targetComponent','owner','migrationStatus','verificationMethod','runtimeEvidence','knownDifferences','decision'
$expected = (Import-Csv -LiteralPath $sourcePath | Where-Object { $_.File -notmatch '(^|\\)_archive(\\|$)' } | Select-Object -ExpandProperty Command -Unique).Count
if ($ledger.schemaVersion -ne 1) { throw 'CAPABILITY_LEDGER_SCHEMA_VERSION_INVALID' }
if ($ledger.capabilities.Count -ne $expected) { throw "CAPABILITY_LEDGER_COUNT_MISMATCH: expected $expected, got $($ledger.capabilities.Count)" }
if (($ledger.capabilities.id | Select-Object -Unique).Count -ne $ledger.capabilities.Count) { throw 'CAPABILITY_LEDGER_DUPLICATE_ID' }
foreach ($capability in $ledger.capabilities) { foreach ($field in $required) { if (-not $capability.PSObject.Properties.Name.Contains($field)) { throw "CAPABILITY_LEDGER_REQUIRED_FIELD_MISSING: $($capability.id):$field" } } }
$accepted = @($ledger.capabilities | Where-Object { $_.decision -eq 'MIGRATED_RUNTIME_VERIFIED' })
if ($accepted.Count -ne 2 -or ($accepted.id -notcontains 'PLANT3D-2019-CMD-PXP-AAV') -or ($accepted.id -notcontains 'PLANT3D-2019-CMD-PXP-OA')) { throw 'CAPABILITY_LEDGER_REFERENCE_VERTICAL_MISSING' }
Write-Output "CAPABILITY_LEDGER_VALIDATED=$ledgerPath"
Write-Output "CAPABILITY_COUNT=$($ledger.capabilities.Count)"