From odh-ai-helpers
Checks Python package licenses for Red Hat/Fedora redistribution compatibility by cloning source repos, normalizing to SPDX IDs, and producing structured verdicts with escalation guidance.
npx claudepluginhub opendatahub-io/ai-helpers --plugin odh-ai-helpersThis skill is limited to using the following tools:
Evaluate whether a Python package license permits redistribution in Red Hat products.
Scans repository dependencies, vendored code, fonts, and assets for licenses, producing per-package verdicts on commercial use: ready, citation required, needs info, or not allowed.
Finds license information for Python packages by checking PyPI metadata first, then shallow-cloning Git repos to search LICENSE files. Use for deterministic license detection.
Guides open source license compliance: evaluates dependencies, analyzes compatibility between licenses, tracks obligations, and supports attribution and workflows for distribution.
Share bugs, ideas, or general feedback.
Evaluate whether a Python package license permits redistribution in Red Hat products.
Policy basis: Red Hat redistribution policy follows the Fedora Linux license policy. Use the Fedora License Data as the authoritative source -- NOT OSI-approval status.
Use the python-packaging-source-finder skill to find the upstream repo URL:
Skill: python-packaging-source-finder
Then use the git-shallow-clone skill to clone the repository locally:
Skill: git-shallow-clone
This gives a local path to the cloned repo for deterministic license inspection.
Search the cloned repo root for license files in this order: LICENSE, LICENSE.txt, LICENSE.md, COPYING, COPYING.txt, LICENCE, LICENCE.txt
ls <clone_path>/{LICENSE,COPYING,LICENCE}{,.txt,.md} 2>/dev/null | head -1
Read the license file (up to 128 KB) and map to an SPDX identifier. If the file exceeds 128 KB, read only the first 128 KB and note the truncation. If the file cannot be read within 30 seconds, treat it as unreadable and set Verdict: NEEDS-HUMAN-REVIEW with a note explaining the timeout.
Also check pyproject.toml or setup.cfg for a license field as a secondary
signal. If the PyPI metadata license differs from the source file, set
Source verified: MISMATCH (PyPI: [X], repo: [Y]) and use the source file as
authoritative.
Normalise to SPDX ID using exact then fuzzy matching:
Compound expressions: If the license contains AND or OR:
AND: split into components; every component must be individually allowed.
If any single component is not-allowed, the whole expression is BLOCKED.
Document the split in Notes.OR: evaluate each component; use the most permissive allowed one. If none are
allowed, the overall verdict is the best individual verdict.If no license file found and no SPDX mapping possible: Verdict: NEEDS-HUMAN-REVIEW.
Clean up the clone when done:
if [ -n "${CLONE_DIR}" ] && echo "${CLONE_DIR}" | grep -q '^/tmp/'; then
rm -rf -- "${CLONE_DIR}"
else
echo "ERROR: refusing to delete '${CLONE_DIR}' -- not under /tmp/" >&2
fi
Fetch the live data:
WebFetch: https://gitlab.com/fedora/legal/fedora-license-data/-/jobs/artifacts/main/raw/fedora-licenses.json?job=json
Search for an entry matching the SPDX ID (case-insensitive on spdx_abbrev).
Each entry has a status field which may be a string or an array. Normalise to
an array and apply the following deterministic mapping.
Fedora License Data defines these status values:
| Status | Meaning | Outcome |
|---|---|---|
allowed | Approved for Fedora packages | Proceed to Step 4 |
allowed-fonts | Approved only for font packages | Proceed to Step 4 (add note: "License approved for fonts only; verify component type") |
allowed-documentation | Approved only for documentation | Proceed to Step 4 (add note: "License approved for documentation only; verify component type") |
allowed-content | Approved only for content (non-code) | Proceed to Step 4 (add note: "License approved for content only; verify component type") |
not-allowed | Explicitly prohibited | Verdict: BLOCKED |
Apply fail-closed logic:
"not-allowed" -> Verdict: BLOCKEDallowed* statuses above (and none
"not-allowed") -> proceed to Step 4status field is missing, empty, or contains a value not listed in
the table above -> Verdict: NEEDS-HUMAN-REVIEW (note the unrecognised value)Fallback table (use only when the JSON fetch fails):
| Status | SPDX identifiers (representative) |
|---|---|
| allowed | MIT, Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, PSF-2.0, CC0-1.0 |
| allowed | GPL-2.0-only, GPL-2.0-or-later, GPL-3.0-only, GPL-3.0-or-later |
| allowed | LGPL-2.0-only, LGPL-2.1-only, LGPL-2.1-or-later, LGPL-3.0-or-later |
| allowed | AGPL-3.0-only, AGPL-3.0-or-later, MPL-2.0, EUPL-1.2, EPL-2.0 |
| not-allowed | SSPL-1.0, BUSL-1.1, Commons-Clause |
| ESCALATE | Any custom/proprietary license not in SPDX |
When using fallback, append to Notes: "Fedora License Data unreachable; fallback table used. Verify at https://docs.fedoraproject.org/en-US/legal/allowed-licenses/"
Rule A -- Proprietary or custom license (not in Fedora data):
First check the Vendor Agreements table below. If covered by an agreement: Verdict: ALLOWED, Notes: "Covered by Red Hat [Vendor] redistribution agreement."
Otherwise:
Rule B -- AGPL-3.0 (status=allowed, SPDX is AGPL-3.0-only or -or-later): Verdict: ALLOWED-WITH-CAVEAT. Notes: "AGPL-3.0 is allowed for redistribution but is frequently part of a dual-licensing strategy, increasing risk. PM sign-off required."
Rule C -- All other allowed licenses: Verdict: ALLOWED. Notes: omit unless compliance note needed (e.g. LGPL dynamic linking does not propagate copyleft).
Rule D -- Not-allowed: Verdict: BLOCKED. Notes: "License is on the Fedora not-allowed list."
Build compliance (independent of license verdict):
If source_available is false or no buildable source exists (no setup.py,
pyproject.toml, or equivalent):
Export compliance (independent of license verdict): Check the upstream org's primary country against these authoritative machine-readable sources:
For each source checked, record the following metadata:
source_url: the URL fetchedretrieved_at: ISO 8601 timestamp of the fetchfetch_outcome: success or failureretry_count: number of retries attempted (0 if first attempt succeeded)If flagged by any source:
If sources are unreachable after 2 retries:
Otherwise: Export compliance: OK
Produce ONLY this six-field block. No preamble, no extra text.
_License:_ [SPDX expression, or "non-SPDX: [raw string]" if unmappable]
_Source verified:_ YES | NO | MISMATCH (PyPI: [X], repo: [Y])
_Verdict:_ ALLOWED | ALLOWED-WITH-CAVEAT | NEEDS-HUMAN-REVIEW | ESCALATE-TO-LEGAL | BLOCKED
_Build compliance:_ OK | BUILD-COMPLIANCE-FLAG
_Export compliance:_ OK | EXPORT-COMPLIANCE-FLAG
_Notes:_ [Actionable guidance. Omit if verdict is ALLOWED and all flags are OK.]
| Vendor | Covered components |
|---|---|
| NVIDIA | CUDA libraries, runtimes, NCCL, and related NVIDIA components |
| Intel Gaudi | Gaudi AI accelerator software and libraries |
| IBM Spyre | IBM Spyre AI hardware and associated software components |
| Scenario | Verdict | Build |
|---|---|---|
MIT, Apache-2.0, BSD-*, ISC, PSF-2.0 | ALLOWED | OK |
GPL-*, LGPL-* | ALLOWED | OK |
AGPL-3.0-* | ALLOWED-WITH-CAVEAT | OK |
| SSPL-1.0, BUSL-1.1, Commons-Clause | BLOCKED | -- |
| RH-owned proprietary | ESCALATE-TO-LEGAL | -- |
| Third-party proprietary (no vendor agreement) | ESCALATE-TO-LEGAL | -- |
| PyPI/repo license mismatch | per repo license (MISMATCH noted) | OK |
| No buildable source | per license | FLAG |