feat: route legacy configuration probe through CLI

This commit is contained in:
codex
2026-08-06 07:40:51 +03:00
parent 84ddac90a8
commit 0b890432d5
3 changed files with 75 additions and 15 deletions
@@ -1,4 +1,5 @@
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Launcher.Next.Core;
@@ -17,31 +18,34 @@ public sealed class LegacyConfigurationProbe(LegacyConfigurationAdapter adapter)
var selectionPath = request.DownloadList.Value;
var overlayPath = request.Overlay.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");
if (!File.Exists(settingsPath) || !File.Exists(selectionPath) || !File.Exists(overlayPath)) throw new InvalidOperationException("LEGACY_PROBE_INPUT_PATH_INVALID");
if (evidenceRoot.StartsWith(@"\\", StringComparison.Ordinal) || SameOrNested(evidenceRoot, Path.GetDirectoryName(settingsPath)!) || SameOrNested(evidenceRoot, Path.GetDirectoryName(selectionPath)!) || SameOrNested(evidenceRoot, Path.GetDirectoryName(overlayPath)!)) throw new InvalidOperationException("LEGACY_PROBE_PATH_COLLISION");
Directory.CreateDirectory(evidenceRoot);
var before = await Snapshot(settingsPath, selectionPath, overlayPath, cancellationToken);
try
{
var settings = adapter.ParseSettings(await File.ReadAllTextAsync(settingsPath, cancellationToken));
var settings = adapter.ParseSettings(await ReadOnlyTextAsync(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 overlay = LegacyCompatibilityOverlayParser.Parse(await File.ReadAllTextAsync(overlayPath, cancellationToken));
var requirements = adapter.Join(pattern[0], adapter.ParseDownloadList(await File.ReadAllTextAsync(selectionPath, cancellationToken)), overlay);
var overlay = LegacyCompatibilityOverlayParser.Parse(await ReadOnlyTextAsync(overlayPath, cancellationToken));
var requirements = adapter.Join(pattern[0], adapter.ParseDownloadList(await ReadOnlyTextAsync(selectionPath, cancellationToken)), overlay);
var after = await Snapshot(settingsPath, selectionPath, overlayPath, 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,[]);
var output = await WriteEvidenceAsync(evidenceRoot, request.OperationId, new { evidenceSchemaVersion=1, evidenceKind="legacy-configuration-probe", mode="LegacyConfigurationProbe", operationId=request.OperationId, terminalState="Succeeded", exitClassification="Success", selectedPattern=pattern[0].Name, selectedIdCount=requirements.Count, requirements, inputs=before.Select(x=>Redact(x,request.OperationId)), sideEffects=SideEffects() }, cancellationToken);
return new("Succeeded",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]);
var code=ex.Message.StartsWith("LEGACY_",StringComparison.Ordinal)||ex.Message.StartsWith("AMBIGUOUS_",StringComparison.Ordinal)?ex.Message:"LEGACY_PROBE_INTERNAL_FAILURE";
var output=await WriteEvidenceAsync(evidenceRoot,request.OperationId,new { evidenceSchemaVersion=1,evidenceKind="legacy-configuration-probe",mode="LegacyConfigurationProbe",operationId=request.OperationId,terminalState="Blocked",exitClassification=code,diagnostics=new[]{code},inputs=before.Select(x=>Redact(x,request.OperationId)),sideEffects=SideEffects() },cancellationToken);
return new("Blocked",[],output,[code]);
}
}
private static async Task<string[]> Snapshot(string a,string b,string c,CancellationToken ct) => [await One(a,ct),await One(b,ct),await One(c,ct)];
private static async Task<string> 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)); }
private static object SideEffects()=>new { sourceWrites=false,payloadCopied=false,stagingCreated=false,packageCommitted=false,activationAttempted=false,pointerMutationAttempted=false,autoCadLaunchAttempted=false,dfsWriteAttempted=false };
private static bool SameOrNested(string child,string parent) { var c=Path.GetFullPath(child).TrimEnd(Path.DirectorySeparatorChar)+Path.DirectorySeparatorChar; var p=Path.GetFullPath(parent).TrimEnd(Path.DirectorySeparatorChar)+Path.DirectorySeparatorChar; return c.StartsWith(p,StringComparison.OrdinalIgnoreCase); }
private static async Task<string[]> Snapshot(string a,string b,string c,CancellationToken ct) => [await One("LegacySettingsPath",a,ct),await One("LegacyDownloadListPath",b,ct),await One("LegacyCompatibilityOverlayPath",c,ct)];
private static async Task<string> One(string role,string path,CancellationToken ct) { var info=new FileInfo(path); if(info.Attributes.HasFlag(FileAttributes.ReparsePoint)) throw new InvalidOperationException("LEGACY_PROBE_REPARSE_SOURCE_REJECTED"); await using var stream=new FileStream(path,FileMode.Open,FileAccess.Read,FileShare.Read); return string.Join('|',role,Path.GetFileName(path),info.Length,info.LastWriteTimeUtc.Ticks,Convert.ToHexString(await SHA256.HashDataAsync(stream,ct)),path); }
private static async Task<string> ReadOnlyTextAsync(string path,CancellationToken ct) { await using var stream=new FileStream(path,FileMode.Open,FileAccess.Read,FileShare.Read); using var reader=new StreamReader(stream,Encoding.UTF8,true); return await reader.ReadToEndAsync(ct); }
private static object Redact(string value,string operationId) { var parts=value.Split('|'); return new { role=parts[0],basename=parts[1],length=parts[2],lastWriteUtcTicks=parts[3],sha256=parts[4],pathFingerprint=Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(operationId+"|"+parts[5]))) }; }
private static async Task<string> WriteEvidenceAsync(string root,string operationId,object evidence,CancellationToken ct) { var operation=Path.Combine(root,operationId); Directory.CreateDirectory(operation); var final=Path.Combine(operation,"legacy-configuration-probe.json"); var temp=final+".tmp"; await File.WriteAllTextAsync(temp,JsonSerializer.Serialize(evidence),ct); File.Move(temp,final,true); return final; }
}