license_policy.py file-level

at sha256:1 · View file ↗ · Intel ↗

History
1 files
1 commits
0 hotspots
0 🧊 dead
0 💥 blast risk
sha256:c Add T4 job cancellation retry and validation · · Jun 11, 2026
1 """License and source-path policy for Scooling Lab dependency evidence."""
2
3 from __future__ import annotations
4
5 from dataclasses import dataclass
6
7
8 ALLOWED_LICENSES: frozenset[str] = frozenset(
9 {
10 "Apache-2.0",
11 "MIT",
12 "BSD-2-Clause",
13 "BSD-3-Clause",
14 "ISC",
15 "PSF-2.0",
16 }
17 )
18
19 BLOCKED_LICENSES: frozenset[str] = frozenset(
20 {
21 "AGPL-3.0",
22 "AGPL-3.0-only",
23 "AGPL-3.0-or-later",
24 "GPL-2.0",
25 "GPL-2.0-only",
26 "GPL-2.0-or-later",
27 "GPL-3.0",
28 "GPL-3.0-only",
29 "GPL-3.0-or-later",
30 }
31 )
32
33 BLOCKED_PATH_SEGMENTS: frozenset[str] = frozenset({"studio", "unsloth_cli"})
34
35
36 class LicensePolicyError(ValueError):
37 """Raised when a dependency or source path violates Scooling Lab policy."""
38
39
40 @dataclass(frozen=True)
41 class BomEntry:
42 """Single dependency or project inventory row used by the BOM generator."""
43
44 name: str
45 version: str
46 license: str
47 source_path: str
48 evidence: str
49
50
51 def normalize_source_path(source_path: str) -> str:
52 """Normalize a source path for policy checks without touching the filesystem."""
53
54 stripped = source_path.strip().replace("\\", "/")
55 while "//" in stripped:
56 stripped = stripped.replace("//", "/")
57 return stripped.strip("/")
58
59
60 def validate_source_path(source_path: str) -> None:
61 """Reject source paths that enter AGPL-covered Unsloth Studio or CLI code."""
62
63 normalized = normalize_source_path(source_path)
64 segments = {segment for segment in normalized.split("/") if segment}
65 blocked = segments.intersection(BLOCKED_PATH_SEGMENTS)
66 if blocked:
67 blocked_list = ", ".join(sorted(blocked))
68 raise LicensePolicyError(f"blocked source path segment: {blocked_list}")
69
70
71 def validate_license(license_id: str) -> None:
72 """Reject non-allowlisted licenses before code can enter CI or the BOM."""
73
74 normalized = license_id.strip()
75 if normalized in BLOCKED_LICENSES:
76 raise LicensePolicyError(f"blocked license: {normalized}")
77 if normalized not in ALLOWED_LICENSES:
78 raise LicensePolicyError(f"non-allowlisted license: {normalized}")
79
80
81 def validate_entry(entry: BomEntry) -> None:
82 """Validate one BOM entry against license and source-path policy."""
83
84 if not entry.name.strip():
85 raise LicensePolicyError("BOM entry name is required")
86 if not entry.version.strip():
87 raise LicensePolicyError("BOM entry version is required")
88 if not entry.evidence.strip():
89 raise LicensePolicyError("BOM entry evidence is required")
90 validate_license(entry.license)
91 validate_source_path(entry.source_path)
92
93
94 def validate_entries(entries: list[BomEntry]) -> None:
95 """Validate all BOM entries and fail closed when the BOM is empty."""
96
97 if not entries:
98 raise LicensePolicyError("BOM must contain at least one entry")
99 for entry in entries:
100 validate_entry(entry)