feat: establish p-rec-annot composite module manifest

This commit is contained in:
codex
2026-08-07 09:06:49 +03:00
parent 91f316d79b
commit 6e909f89bc
5 changed files with 39 additions and 1 deletions
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<ApplicationPackage SchemaVersion="1.0" Name="P_Rec_Annot" Description="P_Rec_Annot Plant 3D 2026" AppVersion="0.1.0" Author="NBIM"> <ApplicationPackage SchemaVersion="1.0" Name="P_Rec_Annot" Description="P_Rec_Annot Plant 3D 2026" AppVersion="0.2.0" ProductCode="b455ee6eaed79d76f084846a60ce3284" Author="NBIM">
<Components> <Components>
<!-- SeriesMin/SeriesMax and platform token are pending official/runtime verification. --> <!-- SeriesMin/SeriesMax and platform token are pending official/runtime verification. -->
<RuntimeRequirements OS="Win64" /> <RuntimeRequirements OS="Win64" />
@@ -0,0 +1,27 @@
using System.Security.Cryptography;
using System.Text.Json;
namespace PRecAnnot.Plant3D2026;
internal sealed record CompositeAssemblyManifest(string Name, string Sha256, bool Required);
internal sealed record CompositeManifest(int SchemaVersion, string Package, string Version, IReadOnlyList<string> Modules, IReadOnlyList<CompositeAssemblyManifest> Assemblies);
internal static class CompositeModuleManifest
{
public static CompositeManifest Validate()
{
var assemblyPath = typeof(EntryPoint).Assembly.Location;
var path = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(assemblyPath)!, "..", "module-manifest.json"));
if (!File.Exists(path)) throw new InvalidOperationException("COMPOSITE_MODULE_MANIFEST_MISSING");
var manifest = JsonSerializer.Deserialize<CompositeManifest>(File.ReadAllText(path), new JsonSerializerOptions { PropertyNameCaseInsensitive = true }) ?? throw new InvalidOperationException("COMPOSITE_MODULE_MANIFEST_INVALID");
if (manifest.SchemaVersion != 1 || !string.Equals(manifest.Package, "P_Rec_Annot", StringComparison.Ordinal) || manifest.Modules.Count == 0) throw new InvalidOperationException("COMPOSITE_MODULE_MANIFEST_INVALID");
foreach (var item in manifest.Assemblies.Where(value => value.Required))
{
var file = Path.Combine(Path.GetDirectoryName(assemblyPath)!, item.Name);
if (!File.Exists(file)) throw new InvalidOperationException($"COMPOSITE_MODULE_ASSEMBLY_MISSING:{item.Name}");
var hash = Convert.ToHexString(SHA256.HashData(File.ReadAllBytes(file))).ToLowerInvariant();
if (!string.Equals(hash, item.Sha256, StringComparison.OrdinalIgnoreCase)) throw new InvalidOperationException($"COMPOSITE_MODULE_HASH_MISMATCH:{item.Name}");
}
return manifest;
}
}
@@ -15,6 +15,8 @@ public sealed class EntryPoint : IExtensionApplication
public void Initialize() public void Initialize()
{ {
RuntimeDiagnostics.Write("AssemblyInitializeStarted"); RuntimeDiagnostics.Write("AssemblyInitializeStarted");
var manifest = CompositeModuleManifest.Validate();
RuntimeDiagnostics.Write("CompositeModulesValidated", resultStatus: "Validated", details: new Dictionary<string, object?> { ["PackageVersion"] = manifest.Version, ["ModuleCount"] = manifest.Modules.Count });
RibbonIntegration.Register(); RibbonIntegration.Register();
// Test automation is driven only by the explicitly generated /b script. // Test automation is driven only by the explicitly generated /b script.
// Initialize remains production-equivalent and must not orchestrate commands. // Initialize remains production-equivalent and must not orchestrate commands.
@@ -8,4 +8,8 @@ New-Item -ItemType Directory -Force -Path $contents | Out-Null
$output=Join-Path $PSScriptRoot "..\src\PRecAnnot.Plant3D2026\bin\$Configuration\net8.0-windows" $output=Join-Path $PSScriptRoot "..\src\PRecAnnot.Plant3D2026\bin\$Configuration\net8.0-windows"
Get-ChildItem $output -File -Filter '*.dll' | Copy-Item -Destination $contents -Force Get-ChildItem $output -File -Filter '*.dll' | Copy-Item -Destination $contents -Force
Copy-Item (Join-Path $PSScriptRoot '..\bundle\P_Rec_Annot.bundle\PackageContents.xml') $bundleRoot -Force Copy-Item (Join-Path $PSScriptRoot '..\bundle\P_Rec_Annot.bundle\PackageContents.xml') $bundleRoot -Force
$modules=@('PHP_REC_ANNOT','AnnotateLineNumbers','AutoLinenumberConnections','AutoElevation','InsertElevation','ExtentsIntersect','DataLinksRead')
$assemblies=Get-ChildItem $contents -File -Filter '*.dll' | Sort-Object Name | ForEach-Object { [ordered]@{ name=$_.Name; sha256=(Get-FileHash $_.FullName -Algorithm SHA256).Hash.ToLowerInvariant(); required=$true } }
$manifest=[ordered]@{ schemaVersion=1; package='P_Rec_Annot'; version='0.2.0'; modules=$modules; assemblies=$assemblies }
$manifest | ConvertTo-Json -Depth 5 | Set-Content (Join-Path $bundleRoot 'Contents\module-manifest.json') -Encoding utf8
Write-Host 'Bundle assembly build completed; deployment is intentionally not performed.' Write-Host 'Bundle assembly build completed; deployment is intentionally not performed.'
@@ -7,6 +7,11 @@ if(@($entry).Count -ne 1){throw 'Bundle must have exactly one entry assembly.'}
if($entry.ModuleName -match '_netload|OrthoLoader|Settings.xml'){throw 'Legacy loading chain is forbidden.'} if($entry.ModuleName -match '_netload|OrthoLoader|Settings.xml'){throw 'Legacy loading chain is forbidden.'}
if($entry.ModuleName -notmatch 'PRecAnnot\.Plant3D2026\.dll'){throw 'Unexpected entry assembly.'} if($entry.ModuleName -notmatch 'PRecAnnot\.Plant3D2026\.dll'){throw 'Unexpected entry assembly.'}
if(-not (Test-Path (Join-Path $BundleRoot 'Contents\Windows\PRecAnnot.Plant3D2026.dll'))){throw 'Entry assembly is missing.'} if(-not (Test-Path (Join-Path $BundleRoot 'Contents\Windows\PRecAnnot.Plant3D2026.dll'))){throw 'Entry assembly is missing.'}
$moduleManifestPath=Join-Path $BundleRoot 'Contents\module-manifest.json'
if(-not (Test-Path $moduleManifestPath)){throw 'Composite module manifest is missing.'}
$moduleManifest=Get-Content $moduleManifestPath -Raw | ConvertFrom-Json
if($moduleManifest.package -ne 'P_Rec_Annot' -or $moduleManifest.version -ne $xml.ApplicationPackage.AppVersion){throw 'Composite module manifest identity/version mismatch.'}
foreach($assembly in $moduleManifest.assemblies){$path=Join-Path $BundleRoot "Contents\Windows\$($assembly.name)"; if(-not (Test-Path $path)){throw "Manifest assembly missing: $($assembly.name)"}; if((Get-FileHash $path -Algorithm SHA256).Hash.ToLowerInvariant() -ne $assembly.sha256){throw "Manifest hash mismatch: $($assembly.name)"}}
$forbidden='^(AcMgd|AcDbMgd|AcCoreMgd|AdWindows|PnP.*)\.dll$' $forbidden='^(AcMgd|AcDbMgd|AcCoreMgd|AdWindows|PnP.*)\.dll$'
$forbiddenFiles=Get-ChildItem $BundleRoot -Recurse -File | Where-Object { $_.Name -match $forbidden -or $_.Extension -in '.pdb','.cs' -or $_.FullName -match '\\(bin|obj)\\' } $forbiddenFiles=Get-ChildItem $BundleRoot -Recurse -File | Where-Object { $_.Name -match $forbidden -or $_.Extension -in '.pdb','.cs' -or $_.FullName -match '\\(bin|obj)\\' }
if($forbiddenFiles){throw "Bundle contains forbidden files: $($forbiddenFiles.FullName -join ', ')"} if($forbiddenFiles){throw "Bundle contains forbidden files: $($forbiddenFiles.FullName -join ', ')"}