From 02e34186fcf60ad0c475d8008a5ad89e821e257f Mon Sep 17 00:00:00 2001 From: codex Date: Wed, 5 Aug 2026 16:00:56 +0300 Subject: [PATCH] feat: add isolated legacy configuration probe --- .../LegacyConfigurationProbe.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 Launcher.Next.Application/LegacyConfigurationProbe.cs diff --git a/Launcher.Next.Application/LegacyConfigurationProbe.cs b/Launcher.Next.Application/LegacyConfigurationProbe.cs new file mode 100644 index 0000000..8db9f5b --- /dev/null +++ b/Launcher.Next.Application/LegacyConfigurationProbe.cs @@ -0,0 +1,46 @@ +using System.Security.Cryptography; +using System.Text.Json; +using Launcher.Next.Core; + +namespace Launcher.Next.Application; + +public sealed record LegacyConfigurationProbeRequest(LauncherPathVariables Paths, string PatternName, LegacyCompatibilityOverlay Overlay, string OperationId); +public sealed record LegacyConfigurationProbeResult(string TerminalState, IReadOnlyList Requirements, string EvidencePath, IReadOnlyList Diagnostics); + +/// Read-only configuration inspection. It intentionally has no package, +/// activation, process, or Autodesk dependency. +public sealed class LegacyConfigurationProbe(LegacyConfigurationAdapter adapter, IPathResolver resolver) +{ + public async Task RunAsync(LegacyConfigurationProbeRequest request, CancellationToken cancellationToken = default) + { + var paths = resolver.Resolve(request.Paths, ["LegacySettingsPath", "LegacyDownloadListPath", "LegacyProbeEvidenceRoot"]); + var settingsPath = resolver.ResolvePath(paths.Require("LegacySettingsPath"), request.Paths); + var selectionPath = resolver.ResolvePath(paths.Require("LegacyDownloadListPath"), request.Paths); + var evidenceRoot = resolver.ResolvePath(paths.Require("LegacyProbeEvidenceRoot"), request.Paths); + if (!File.Exists(settingsPath) || !File.Exists(selectionPath) || Path.GetFullPath(evidenceRoot).StartsWith(@"\\", StringComparison.Ordinal)) throw new InvalidOperationException("LEGACY_PROBE_INPUT_PATH_INVALID"); + if (Path.GetDirectoryName(settingsPath)!.Equals(evidenceRoot, StringComparison.OrdinalIgnoreCase) || Path.GetDirectoryName(selectionPath)!.Equals(evidenceRoot, StringComparison.OrdinalIgnoreCase)) throw new InvalidOperationException("PATH_ROLE_COLLISION"); + Directory.CreateDirectory(evidenceRoot); + var before = await Snapshot(settingsPath, selectionPath, cancellationToken); + try + { + var settings = adapter.ParseSettings(await File.ReadAllTextAsync(settingsPath, cancellationToken)); + var pattern = settings.Patterns.Where(x => string.Equals(x.Name, request.PatternName, StringComparison.Ordinal)).ToArray(); + if (pattern.Length != 1) throw new InvalidOperationException(pattern.Length == 0 ? "LEGACY_PATTERN_NOT_FOUND" : "LEGACY_PATTERN_AMBIGUOUS"); + var requirements = adapter.Join(pattern[0], adapter.ParseDownloadList(await File.ReadAllTextAsync(selectionPath, cancellationToken)), request.Overlay); + var after = await Snapshot(settingsPath, selectionPath, cancellationToken); + if (!before.SequenceEqual(after)) throw new InvalidOperationException("LEGACY_PROBE_SOURCE_CHANGED_DURING_READ"); + var evidence = new { mode="LegacyConfigurationProbe", operationId=request.OperationId, terminalState="LEGACY_CONFIGURATION_VERIFIED", selectedPattern=pattern[0].Name, selectedIdCount=requirements.Count, sourceWrites=false, payloadCopied=false, stagingCreated=false, activationAttempted=false, autoCadLaunchAttempted=false, dfsWriteAttempted=false, inputSnapshots=before.Select(x=>Convert.ToHexString(SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(x)))) }; + var output=Path.Combine(evidenceRoot, request.OperationId + ".json"); + await File.WriteAllTextAsync(output, JsonSerializer.Serialize(evidence), cancellationToken); + return new("LEGACY_CONFIGURATION_VERIFIED",requirements,output,[]); + } + catch(Exception ex) + { + var output=Path.Combine(evidenceRoot, request.OperationId + ".json"); + await File.WriteAllTextAsync(output, JsonSerializer.Serialize(new { mode="LegacyConfigurationProbe", operationId=request.OperationId, terminalState="PILOT_CONFIGURATION_BLOCKED", diagnostic=ex.Message, sourceWrites=false, payloadCopied=false, stagingCreated=false, activationAttempted=false, autoCadLaunchAttempted=false, dfsWriteAttempted=false }), cancellationToken); + return new("PILOT_CONFIGURATION_BLOCKED",[],output,[ex.Message]); + } + } + private static async Task Snapshot(string a,string b,CancellationToken ct) => [await One(a,ct),await One(b,ct)]; + private static async Task One(string path,CancellationToken ct) { var info=new FileInfo(path); return path+"|"+info.Length+"|"+info.LastWriteTimeUtc.Ticks+"|"+Convert.ToHexString(await SHA256.HashDataAsync(File.OpenRead(path),ct)); } +}