namespace StellaOps.Policy.Licensing; public sealed record LicenseCompatibilityResult(bool IsCompatible, string? Reason); public sealed class LicenseCompatibilityChecker { public LicenseCompatibilityResult Check( LicenseDescriptor first, LicenseDescriptor second, ProjectContext context) { if (first is null) { throw new ArgumentNullException(nameof(first)); } if (second is null) { throw new ArgumentNullException(nameof(second)); } if (IsApacheGpl2Conflict(first.Id, second.Id)) { return new LicenseCompatibilityResult( false, "Apache-2.0 is incompatible with GPL-2.0-only due to patent clauses."); } if (first.Category == LicenseCategory.Proprietary && second.Category == LicenseCategory.StrongCopyleft) { return new LicenseCompatibilityResult( false, "Strong copyleft is incompatible with proprietary licensing."); } if (second.Category == LicenseCategory.Proprietary && first.Category == LicenseCategory.StrongCopyleft) { return new LicenseCompatibilityResult( false, "Strong copyleft is incompatible with proprietary licensing."); } if (context.DistributionModel == DistributionModel.Commercial && first.Category == LicenseCategory.StrongCopyleft && second.Category == LicenseCategory.StrongCopyleft) { return new LicenseCompatibilityResult( true, "Strong copyleft pairing detected; ensure redistribution obligations are met."); } return new LicenseCompatibilityResult(true, null); } private static bool IsApacheGpl2Conflict(string first, string second) { return (IsApache(first) && IsGpl2Only(second)) || (IsApache(second) && IsGpl2Only(first)); } private static bool IsApache(string licenseId) => licenseId.Equals("Apache-2.0", StringComparison.OrdinalIgnoreCase); private static bool IsGpl2Only(string licenseId) => licenseId.Equals("GPL-2.0-only", StringComparison.OrdinalIgnoreCase) || licenseId.Equals("GPL-2.0+", StringComparison.OrdinalIgnoreCase); }