feat: gate first limited Apply pilot
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
namespace Launcher.Next.Core;
|
namespace Launcher.Next.Core;
|
||||||
|
|
||||||
public enum LauncherMode { Legacy, DryRun, NextPilot, NextPreferred }
|
public enum LauncherMode { Legacy, DryRun, NextPilot, NextPreferred }
|
||||||
public enum PluginExecutionMode { Smoke, DryRun }
|
public enum PluginExecutionMode { Smoke, DryRun, Apply }
|
||||||
public sealed record ProductInstallation(string ExecutablePath, string ProductVersion, string Architecture, IReadOnlyList<string> Profiles);
|
public sealed record ProductInstallation(string ExecutablePath, string ProductVersion, string Architecture, IReadOnlyList<string> Profiles);
|
||||||
public sealed record PackageFile(string Path, long Length, string Sha256, bool Optional = false);
|
public sealed record PackageFile(string Path, long Length, string Sha256, bool Optional = false);
|
||||||
public sealed record StartupDefinition(string Type, string Command);
|
public sealed record StartupDefinition(string Type, string Command);
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ public sealed record PilotLocalConfiguration
|
|||||||
public bool AllowProcessStart { get; init; }
|
public bool AllowProcessStart { get; init; }
|
||||||
public bool RequireExplicitExecute { get; init; } = true;
|
public bool RequireExplicitExecute { get; init; } = true;
|
||||||
public PluginExecutionMode PluginExecutionMode { get; init; } = PluginExecutionMode.Smoke;
|
public PluginExecutionMode PluginExecutionMode { get; init; } = PluginExecutionMode.Smoke;
|
||||||
|
// Apply is never a normal pilot default. It is accepted only by this
|
||||||
|
// dedicated test branch and still requires the plugin-side fixture permit.
|
||||||
|
public bool AllowApply { get; init; }
|
||||||
public string? PilotRoot { get; init; }
|
public string? PilotRoot { get; init; }
|
||||||
public string? SelectedProductId { get; init; }
|
public string? SelectedProductId { get; init; }
|
||||||
public string? SelectedProfileName { get; init; }
|
public string? SelectedProfileName { get; init; }
|
||||||
@@ -57,7 +60,7 @@ public interface INextPilotSafetyGate { PilotSafetyDecision Evaluate(PilotSafety
|
|||||||
public sealed class NextPilotSafetyGate : INextPilotSafetyGate
|
public sealed class NextPilotSafetyGate : INextPilotSafetyGate
|
||||||
{
|
{
|
||||||
public PilotSafetyDecision Evaluate(PilotSafetyContext c, bool forExecution)
|
public PilotSafetyDecision Evaluate(PilotSafetyContext c, bool forExecution)
|
||||||
{ var v = new List<string>(); if (c.Configuration.LauncherMode != LauncherMode.NextPilot) v.Add("WRONG_LAUNCHER_MODE"); if (!c.Configuration.PilotEnabled) v.Add("PILOT_DISABLED"); if (!c.Configuration.RequireExplicitExecute) v.Add("EXPLICIT_EXECUTE_REQUIRED"); if (c.Configuration.PluginExecutionMode is not (PluginExecutionMode.Smoke or PluginExecutionMode.DryRun)) v.Add("PLUGIN_EXECUTION_MODE_NOT_ALLOWED"); if (c.Product is null) v.Add("PRODUCT_NOT_FOUND"); else if (c.Product.ProductVersion != "2026" || c.Product.Architecture != "x64") v.Add("PRODUCT_NOT_PLANT3D_2026"); if (c.Profile is null) v.Add("PROFILE_NOT_RESOLVED"); if (!c.Root.IsValid) v.Add("PILOT_ROOT_UNTRUSTED"); if (!c.Manifest.IsValid) v.Add("MANIFEST_INVALID"); if (!c.HashesValid) v.Add("HASH_VALIDATION_FAILED"); if (c.AutodeskDllPresent) v.Add("AUTODESK_DLL_PRESENT"); if (c.AcadRunning) v.Add("ACAD_ALREADY_RUNNING"); if (forExecution && !c.Configuration.AllowProcessStart) v.Add("PROCESS_START_DISABLED"); return new(v.Count == 0, v); }
|
{ var v = new List<string>(); if (c.Configuration.LauncherMode != LauncherMode.NextPilot) v.Add("WRONG_LAUNCHER_MODE"); if (!c.Configuration.PilotEnabled) v.Add("PILOT_DISABLED"); if (!c.Configuration.RequireExplicitExecute) v.Add("EXPLICIT_EXECUTE_REQUIRED"); if (c.Configuration.PluginExecutionMode is not (PluginExecutionMode.Smoke or PluginExecutionMode.DryRun or PluginExecutionMode.Apply)) v.Add("PLUGIN_EXECUTION_MODE_NOT_ALLOWED"); if (c.Configuration.PluginExecutionMode == PluginExecutionMode.Apply && !c.Configuration.AllowApply) v.Add("APPLY_NOT_EXPLICITLY_ALLOWED"); if (c.Product is null) v.Add("PRODUCT_NOT_FOUND"); else if (c.Product.ProductVersion != "2026" || c.Product.Architecture != "x64") v.Add("PRODUCT_NOT_PLANT3D_2026"); if (c.Profile is null) v.Add("PROFILE_NOT_RESOLVED"); if (!c.Root.IsValid) v.Add("PILOT_ROOT_UNTRUSTED"); if (!c.Manifest.IsValid) v.Add("MANIFEST_INVALID"); if (!c.HashesValid) v.Add("HASH_VALIDATION_FAILED"); if (c.AutodeskDllPresent) v.Add("AUTODESK_DLL_PRESENT"); if (c.AcadRunning) v.Add("ACAD_ALREADY_RUNNING"); if (forExecution && !c.Configuration.AllowProcessStart) v.Add("PROCESS_START_DISABLED"); return new(v.Count == 0, v); }
|
||||||
}
|
}
|
||||||
public sealed record ProcessLaunchSpecification(string ExecutablePath, IReadOnlyList<string> Arguments, string WorkingDirectory, IReadOnlyDictionary<string,string> EnvironmentVariables);
|
public sealed record ProcessLaunchSpecification(string ExecutablePath, IReadOnlyList<string> Arguments, string WorkingDirectory, IReadOnlyDictionary<string,string> EnvironmentVariables);
|
||||||
public sealed record PilotLaunchPlan(string PlanId, ProductInstallation Product, AutoCadProfileCandidate Profile, string PackageId, string PackageVersion, string ManifestSha256, string PilotRoot, string VersionDirectory, string EntryAssemblyPath, string StartupScriptPath, string StartupScriptSha256, ProcessLaunchSpecification Process, PilotSafetyDecision SafetyDecision);
|
public sealed record PilotLaunchPlan(string PlanId, ProductInstallation Product, AutoCadProfileCandidate Profile, string PackageId, string PackageVersion, string ManifestSha256, string PilotRoot, string VersionDirectory, string EntryAssemblyPath, string StartupScriptPath, string StartupScriptSha256, ProcessLaunchSpecification Process, PilotSafetyDecision SafetyDecision);
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ using Xunit;
|
|||||||
namespace Launcher.Next.Tests;
|
namespace Launcher.Next.Tests;
|
||||||
public sealed class ControlledExecutionTests
|
public sealed class ControlledExecutionTests
|
||||||
{
|
{
|
||||||
|
[Fact] public void ApplyMode_RequiresExplicitAllowApply()
|
||||||
|
{ var c = new PilotLocalConfiguration { SchemaVersion = 1, LauncherMode = LauncherMode.NextPilot, PilotEnabled = true, AllowProcessStart = true, RequireExplicitExecute = true, PluginExecutionMode = PluginExecutionMode.Apply }; var d = new NextPilotSafetyGate().Evaluate(new(c, new("C:\\acad.exe", "2026", "x64", []), new("Pilot", false, true, ["registry"]), new(true, "C:\\AutoCAD User Support\\LauncherNextPilot", []), ValidationResult.Success, true, false, false), true); Assert.Contains("APPLY_NOT_EXPLICITLY_ALLOWED", d.Violations); }
|
||||||
|
[Fact] public void ApplyMode_WithExplicitAllowApply_IsAllowedByLauncherGate()
|
||||||
|
{ var c = new PilotLocalConfiguration { SchemaVersion = 1, LauncherMode = LauncherMode.NextPilot, PilotEnabled = true, AllowProcessStart = true, AllowApply = true, RequireExplicitExecute = true, PluginExecutionMode = PluginExecutionMode.Apply }; var d = new NextPilotSafetyGate().Evaluate(new(c, new("C:\\acad.exe", "2026", "x64", []), new("Pilot", false, true, ["registry"]), new(true, "C:\\AutoCAD User Support\\LauncherNextPilot", []), ValidationResult.Success, true, false, false), true); Assert.True(d.IsAllowed); }
|
||||||
[Fact] public void MissingPilotConfiguration_DefaultsToDryRun() => Assert.Equal(LauncherMode.DryRun, PilotConfigurationParser.Parse(null).Configuration.LauncherMode);
|
[Fact] public void MissingPilotConfiguration_DefaultsToDryRun() => Assert.Equal(LauncherMode.DryRun, PilotConfigurationParser.Parse(null).Configuration.LauncherMode);
|
||||||
[Fact] public void InvalidPilotConfiguration_BlocksPilot() => Assert.False(PilotConfigurationParser.Parse("{").IsValid);
|
[Fact] public void InvalidPilotConfiguration_BlocksPilot() => Assert.False(PilotConfigurationParser.Parse("{").IsValid);
|
||||||
[Fact] public void PilotRoot_Unc_IsRejected() => Assert.False(new PilotRootPolicy("C:\\AutoCAD User Support").Validate("\\\\server\\share").IsValid);
|
[Fact] public void PilotRoot_Unc_IsRejected() => Assert.False(new PilotRootPolicy("C:\\AutoCAD User Support").Validate("\\\\server\\share").IsValid);
|
||||||
|
|||||||
Reference in New Issue
Block a user