152 lines
4.7 KiB
Go
Executable File
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)
|
|
}
|