diff --git a/scripts/validation/opcua-lifecycle-memory/ArtifactModuleInfo.cs b/scripts/validation/opcua-lifecycle-memory/ArtifactModuleInfo.cs new file mode 100644 index 00000000..3a74f054 --- /dev/null +++ b/scripts/validation/opcua-lifecycle-memory/ArtifactModuleInfo.cs @@ -0,0 +1,26 @@ +// Read-only managed metadata extraction; does not load or execute product assemblies. +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; +using System.Text.Json; +var root = Path.GetFullPath(args[0]); +var modules = new List(); +foreach (var directory in args.Skip(1)) +{ + foreach (var path in Directory.EnumerateFiles(directory, "*.dll", SearchOption.AllDirectories).Order()) + { + try + { + using var file = File.OpenRead(path); + using var pe = new PEReader(file); + if (!pe.HasMetadata) continue; + var metadata = pe.GetMetadataReader(); + if (!metadata.IsAssembly) continue; + var assembly = metadata.GetAssemblyDefinition(); + modules.Add(new { file = Path.GetRelativePath(root, path).Replace(Path.DirectorySeparatorChar, '/'), + assembly = metadata.GetString(assembly.Name), version = assembly.Version.ToString(), + mvid = metadata.GetGuid(metadata.GetModuleDefinition().Mvid).ToString("D") }); + } + catch (BadImageFormatException) { /* Native DLLs have no CLR module identity. */ } + } +} +Console.WriteLine(JsonSerializer.Serialize(modules)); diff --git a/scripts/validation/opcua-lifecycle-memory/SATURATION.md b/scripts/validation/opcua-lifecycle-memory/SATURATION.md index 9c2af2bd..cbbbaabf 100644 --- a/scripts/validation/opcua-lifecycle-memory/SATURATION.md +++ b/scripts/validation/opcua-lifecycle-memory/SATURATION.md @@ -79,6 +79,9 @@ publishes fresh, separate app/observer artifacts for `linux-arm64`, self-contain .NET **10.0.14**, without trimming or single-file publishing. It never starts the experiment. The bundle records source revision, source hashes, exact publish command, fixed protocol, image digest and SHA-256 of every app/observer/tool file. +It requires the landed HistorianGateway Client/Contracts 1.0.0 dependency graph +and records assembly versions/module MVIDs by reading PE metadata without loading +or executing product assemblies. ```bash python3 scripts/validation/opcua-lifecycle-memory/prepare_saturation.py \ diff --git a/scripts/validation/opcua-lifecycle-memory/prepare_saturation.py b/scripts/validation/opcua-lifecycle-memory/prepare_saturation.py index bb8a907a..979642bd 100644 --- a/scripts/validation/opcua-lifecycle-memory/prepare_saturation.py +++ b/scripts/validation/opcua-lifecycle-memory/prepare_saturation.py @@ -49,12 +49,28 @@ def main(): if not included or any(value["version"] != "10.0.14" for value in included): raise RuntimeError("Published participant is not the prospectively fixed self-contained runtime") dependencies = json.loads((root / "app/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests.deps.json").read_text())["libraries"] + gateway_libraries = [name for name in dependencies if name.startswith("ZB.MOM.WW.HistorianGateway.")] + if not gateway_libraries or any(name.rsplit("/", 1)[-1] != "1.0.0" for name in gateway_libraries): + raise RuntimeError("The reviewed candidate requires the landed HistorianGateway 1.0.0 graph") + metadata = root / "module-info-build" + metadata.mkdir() + (metadata / "module-info.csproj").write_text( + 'Exe' + 'net10.0enable' + 'false' + '') + with (metadata / "build.log").open("w") as log: + subprocess.run(["dotnet", "build", str(metadata / "module-info.csproj"), "-c", "Release", "-m:2"], + stdout=log, stderr=subprocess.STDOUT, check=True) + modules = json.loads(subprocess.check_output(["dotnet", str(metadata / "bin/Release/net10.0/module-info.dll"), + str(root), str(root / "app"), str(root / "observer")], text=True)) receipt = {"protocol": "sdk2-saturation-v1", "sourceRevision": revision, "image": options.image, "rid": "linux-arm64", "runtimeVersion": "10.0.14", "selfContained": True, "canonicalCycles": 384, "completedRestarts": 96, "settlingCycles": 1, "maximumPhaseSeconds": 1800, "windows": [[321, 352], [353, 384]], "observerBufferMiB": 16, "runtimeKeywords": "0x18", "rundown": False, "acceptance": False, "preparedUtc": datetime.now(timezone.utc).isoformat(), "publishCommand": command, "sdkLibraries": [name for name in dependencies if name.startswith("OPCFoundation.")], + "gatewayLibraries": gateway_libraries, "managedModules": modules, "testSourceSha256": sha(project.parent / "Sdk2LifecycleSoakTests.cs"), "observerSourceSha256": sha(source / "SaturationObserver.cs"), "files": manifest} receipt_path = root / "receipt.json"