From 9c49ebf1e2812abf339f4166b3c69011f3e5ad0e Mon Sep 17 00:00:00 2001 From: codex Date: Wed, 5 Aug 2026 16:12:55 +0300 Subject: [PATCH] feat: enforce typed legacy probe input roles --- .gitignore | 1 + .../LegacyConfigurationProbe.cs | 13 ++++++------ Launcher.Next.Core/LauncherPaths.cs | 20 +++++++++++++++++++ ...er.legacy-configuration-probe.example.json | 9 +++++++++ 4 files changed, 36 insertions(+), 7 deletions(-) create mode 100644 launcher.legacy-configuration-probe.example.json diff --git a/.gitignore b/.gitignore index 749f0b1..b7c876c 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,4 @@ PRecAnnot*.dll appsettings.Local.json *.secrets.json .env +launcher.legacy-configuration-probe.json diff --git a/Launcher.Next.Application/LegacyConfigurationProbe.cs b/Launcher.Next.Application/LegacyConfigurationProbe.cs index 8db9f5b..8a71b71 100644 --- a/Launcher.Next.Application/LegacyConfigurationProbe.cs +++ b/Launcher.Next.Application/LegacyConfigurationProbe.cs @@ -4,20 +4,19 @@ using Launcher.Next.Core; namespace Launcher.Next.Application; -public sealed record LegacyConfigurationProbeRequest(LauncherPathVariables Paths, string PatternName, LegacyCompatibilityOverlay Overlay, string OperationId); +public sealed record LegacyConfigurationProbeRequest(ResolvedLegacySettingsPath Settings, ResolvedLegacyDownloadListPath DownloadList, ResolvedLegacyProbeEvidenceRoot EvidenceRoot, 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 sealed class LegacyConfigurationProbe(LegacyConfigurationAdapter adapter) { 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"); + var settingsPath = request.Settings.Value; + var selectionPath = request.DownloadList.Value; + var evidenceRoot = request.EvidenceRoot.Value; + if (!File.Exists(settingsPath) || !File.Exists(selectionPath)) 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); diff --git a/Launcher.Next.Core/LauncherPaths.cs b/Launcher.Next.Core/LauncherPaths.cs index 5fcce57..ef6a144 100644 --- a/Launcher.Next.Core/LauncherPaths.cs +++ b/Launcher.Next.Core/LauncherPaths.cs @@ -27,12 +27,18 @@ public sealed record ResolvedCatalogRoot internal ResolvedCatalogRoot(string value) => Value = value; public string Value { get; } } +public sealed record ResolvedLegacySettingsPath { internal ResolvedLegacySettingsPath(string value) => Value=value; public string Value { get; } } +public sealed record ResolvedLegacyDownloadListPath { internal ResolvedLegacyDownloadListPath(string value) => Value=value; public string Value { get; } } +public sealed record ResolvedLegacyProbeEvidenceRoot { internal ResolvedLegacyProbeEvidenceRoot(string value) => Value=value; public string Value { get; } } public interface IPathResolver { ResolvedLauncherPaths Resolve(LauncherPathVariables variables, IEnumerable? required = null); string ResolvePath(string value, LauncherPathVariables variables, string? constrainedRoot = null); ResolvedCatalogRoot ResolveCatalogRoot(LauncherPathVariables variables); + ResolvedLegacySettingsPath ResolveLegacySettingsPath(LauncherPathVariables variables); + ResolvedLegacyDownloadListPath ResolveLegacyDownloadListPath(LauncherPathVariables variables); + ResolvedLegacyProbeEvidenceRoot ResolveLegacyProbeEvidenceRoot(LauncherPathVariables variables); } public sealed class PathResolver(IReadOnlyDictionary? environment = null) : IPathResolver @@ -92,6 +98,20 @@ public sealed class PathResolver(IReadOnlyDictionary? environme throw new InvalidOperationException("PATH_ROLE_COLLISION"); return new ResolvedCatalogRoot(catalog); } + public ResolvedLegacySettingsPath ResolveLegacySettingsPath(LauncherPathVariables variables) => new(ResolveExistingFile("LegacySettingsPath", variables)); + public ResolvedLegacyDownloadListPath ResolveLegacyDownloadListPath(LauncherPathVariables variables) => new(ResolveExistingFile("LegacyDownloadListPath", variables)); + public ResolvedLegacyProbeEvidenceRoot ResolveLegacyProbeEvidenceRoot(LauncherPathVariables variables) + { + var value=ResolvePath(Resolve(variables,["LegacyProbeEvidenceRoot"]).Require("LegacyProbeEvidenceRoot"),variables); + if(value.StartsWith(@"\\",StringComparison.Ordinal)) throw new InvalidOperationException("LEGACY_PROBE_EVIDENCE_ROOT_NOT_LOCAL"); + return new(value); + } + private string ResolveExistingFile(string role, LauncherPathVariables variables) + { + var value=ResolvePath(Resolve(variables,[role]).Require(role),variables); + if(!File.Exists(value) || Directory.Exists(value)) throw new InvalidOperationException("LEGACY_PROBE_INPUT_NOT_FILE"); + return value; + } private string? ResolveVariable(string name, LauncherPathVariables variables, Dictionary resolved, HashSet stack) { diff --git a/launcher.legacy-configuration-probe.example.json b/launcher.legacy-configuration-probe.example.json new file mode 100644 index 0000000..084c512 --- /dev/null +++ b/launcher.legacy-configuration-probe.example.json @@ -0,0 +1,9 @@ +{ + "schemaVersion": 1, + "mode": "LegacyConfigurationProbe", + "settingsXml": "${LEGACY_PROBE_ROOT}\\Settings.xml", + "downloadListXml": "${LEGACY_PROBE_ROOT}\\download_list.xml", + "compatibilityOverlay": "${LEGACY_PROBE_ROOT}\\legacy-package-overlay.json", + "selectedPattern": "Exact Pattern Name", + "evidenceOutputRoot": "${LEGACY_PROBE_EVIDENCE_ROOT}" +}