sorcery-go/pkg/legal/policy.go

152 lines
4.7 KiB
Go
Executable File

// Package legal is the "Legal Sentinel" — license compliance & policy
// enforcement for the Coven.
//
// It acts as a Pre-Forge Filter: every cast request is intercepted and
// compared against the active Compliance Profile. Three templates ship
// out-of-the-box:
//
// strict_copyleft — FSF/GNU style, blocks all proprietary blobs
// corporate_lite — MIT/Apache friendly, AGPL/SSPL blacklisted
// lawless — "for the brave", silent audit only
//
// The package also generates SBOM exports (SPDX / CycloneDX) so the legal
// department can audit the fleet from a single command.
package legal
import (
"fmt"
"strings"
)
// Policy is the active posture of the Coven.
type Policy struct {
Name string
AllowedFamilies []string
Blacklist []string
FailOnProprietary bool
FailOnCopyleftViral bool
AllowInternalProprietary bool
RequireAttribution bool
SilentMode bool // lawless — log only, never block
AutoAcceptEULA bool
}
// LicenseInfo is what the Grimoire parser yields up about a spell.
type LicenseInfo struct {
SpellName string
License string
IsCopyleft bool
IsProprietary bool
Internal bool
}
// Sentinel is the engine that validates casts.
type Sentinel struct {
Policy Policy
}
// Validate runs the policy against a spell's license. Returns an error if
// the cast must be blocked; nil if it's allowed (possibly with a warning
// stored in `warnings`).
func (s *Sentinel) Validate(info LicenseInfo) (warnings []string, err error) {
// Lawless — never blocks.
if s.Policy.SilentMode {
return nil, nil
}
// 1. Blacklist check.
for _, b := range s.Policy.Blacklist {
if strings.EqualFold(info.License, b) {
return nil, fmt.Errorf("legal: license %s is blacklisted by policy %q", info.License, s.Policy.Name)
}
}
// 2. Proprietary check.
if info.IsProprietary && s.Policy.FailOnProprietary {
if info.Internal && s.Policy.AllowInternalProprietary {
warnings = append(warnings, "internal proprietary spell — verify deployment target")
return warnings, nil
}
return nil, fmt.Errorf("legal: proprietary licenses blocked by policy %q", s.Policy.Name)
}
// 3. Viral copyleft check.
if info.IsCopyleft && s.Policy.FailOnCopyleftViral {
return nil, fmt.Errorf("legal: viral copyleft (%s) blocked by policy %q", info.License, s.Policy.Name)
}
// 4. Allowed families.
if len(s.Policy.AllowedFamilies) > 0 && !contains(s.Policy.AllowedFamilies, familyOf(info.License)) {
if s.Policy.SilentMode {
warnings = append(warnings, fmt.Sprintf("license %s outside allowed families", info.License))
return warnings, nil
}
return nil, fmt.Errorf("legal: license %s not in allowed families %v", info.License, s.Policy.AllowedFamilies)
}
if info.IsCopyleft {
warnings = append(warnings, "copyleft spell — audit required for proprietary Essence integration")
}
return warnings, nil
}
// familyOf reduces a specific license string ("GPL-3.0-only") to its family
// ("GPL") so the policy can be expressed at family granularity.
func familyOf(license string) string {
license = strings.ToUpper(strings.TrimSpace(license))
for _, fam := range []string{"GPL", "LGPL", "AGPL", "MIT", "BSD", "APACHE", "ISC", "MPL", "SSPL", "WTFPL", "UNLICENSE"} {
if strings.HasPrefix(license, fam) {
return fam
}
}
return license
}
func contains(haystack []string, needle string) bool {
for _, h := range haystack {
if strings.EqualFold(h, needle) {
return true
}
}
return false
}
// StrictCopyleft is the FSF/GNU posture.
func StrictCopyleft() Policy {
return Policy{
Name: "strict_copyleft",
AllowedFamilies: []string{"GPL", "LGPL", "MIT", "BSD", "APACHE", "ISC", "MPL", "UNLICENSE"},
Blacklist: []string{"CC-BY-NC-ND", "SSPL", "WTFPL"},
FailOnProprietary: true,
RequireAttribution: true,
}
}
// CorporateLite is the MIT/Apache-friendly posture.
func CorporateLite() Policy {
return Policy{
Name: "corporate_lite",
AllowedFamilies: []string{"MIT", "BSD", "APACHE", "ISC", "UNLICENSE"},
Blacklist: []string{"AGPL", "SSPL"},
FailOnCopyleftViral: true,
AllowInternalProprietary: true,
}
}
// Lawless is the "for the brave" posture — never blocks, only audits.
func Lawless() Policy {
return Policy{
Name: "lawless",
SilentMode: true,
AutoAcceptEULA: true,
}
}
// LoadPolicy returns the matching Policy object for a profile name.
func LoadPolicy(name string) (Policy, error) {
switch name {
case "strict_copyleft":
return StrictCopyleft(), nil
case "corporate_lite":
return CorporateLite(), nil
case "lawless":
return Lawless(), nil
}
return Policy{}, fmt.Errorf("legal: unknown profile %q", name)
}