1. (A)GPL prohibition in module manifests (HARD BLOCK)
SHALL NOT add any package with a GPL-2.0, GPL-3.0, AGPL-3.0, GPL-2.0-or-later,
GPL-3.0-or-later, or AGPL-3.0-or-later license to any module-package.yaml
pip_dependencies list. There is no allowlist path that permits GPL/AGPL in
distributed module manifests. The scripts/license_allowlist.yaml module-manifest
scope exists exclusively for LGPL packages invoked as a subprocess (see Section 3,
“CONDITIONAL”); it does not unblock GPL or AGPL licenses.
Rationale: pip_dependencies in module manifests are installed on end-user
systems via specfact module install. Force-installing GPL software constitutes
a license violation under Apache-2.0 and blocks enterprise/commercial adoption.
Action on violation: Remove the GPL package, run hatch run license-check,
and propose a MIT/Apache-2.0/BSD alternative.
2. (A)GPL in dev env extras (MUST DOCUMENT + PHASE 2 PLAN)
GPL packages in dev-only extras require:
- A
dev-only-scoped entry inscripts/license_allowlist.yamlwith areason. - An explicit Phase 2 removal plan in the
reasonfield. - A comment in
pyproject.tomlat the dependency line.
They are never acceptable in module manifests (see Section 1).
3. Approved licenses for module manifest pip_dependencies
| License | Approved | Notes |
|---|---|---|
| MIT | YES | Unrestricted |
| Apache-2.0 | YES | Unrestricted |
| BSD-2-Clause / BSD-3-Clause | YES | Unrestricted |
| PSF | YES | Unrestricted |
| LGPL-2.1 / LGPL-3.0 | CONDITIONAL | Allowed when invoked as subprocess (not statically linked); requires module-manifest allowlist entry with subprocess justification |
| GPL-2.0 / GPL-3.0 / AGPL | BLOCKED | Never in module manifests; dev-only with allowlist + Phase 2 plan |
4. Reproducible dependency inputs (HARD BLOCK)
uv.lock and requirements/ci/locked.txt are the authoritative frozen inputs for
blocking delivery jobs. They are generated artifacts, not hand-maintained dependency
lists. Any change to pyproject.toml, either frozen file, or the frozen CI setup action
MUST include a reviewed refresh and verification:
hatch run refresh-frozen-delivery
hatch run python scripts/check_reproducible_delivery.py
Review the resulting lock diff and the hash-protected export together. Do not use an
unlocked pip install, uv add, or resolver fallback in a blocking delivery/release
job. A temporary compatibility experiment belongs in the scheduled/manual advisory lane
and cannot replace frozen evidence.
The companion-module fixture at ci/module-fixture.lock.json MUST name the exact
repository and a full, reviewed 40-character commit SHA. Never replace it with a branch,
tag, or PR-head lookup; update it only with accompanying validation evidence.
The BasedPyright runner is a separate committed npm lock at
tools/basedpyright/package-lock.json. CI SHALL install it with npm ci --ignore-scripts
after a SHA-pinned actions/setup-node step; do not add a Python wheel that bundles an
unofficial Node runtime. Flagged dependencies that remain necessary require an exact
version, PyPI artifact URL, artifact SHA-256, source-provenance classification, review
date, expiry, and transitive path in ci/dependency-trust-exceptions.json. Expiry is
fail-closed: renew the review or remove the package. A release with a security or
obfuscation alert is a block entry, not an exception: the dependency-trust checker rejects
it even if a record exists and verifies each remaining record against uv.lock.
Dependency Trust Gate runs for every PR; its matching local pre-commit hook runs before
a dependency-input commit. Socket Security’s Project Report and Pull Request Alerts
are required status checks on protected dev and main branches. Keep both layers: the
native gate prevents known/reviewed artifact drift, while Socket supplies independent
obfuscation and supply-chain analysis.
5. Required gates before any manifest or dependency change is merged
Run these in order:
hatch run license-check # scripts/check_license_compliance.py — exit 0 required
hatch run security-audit # audit the frozen requirements; all unreviewed advisories block
hatch run bandit-scan # bandit -r src/ -ll — review and document findings
6. New pip_dependencies in module manifests — checklist
Before adding a new pip_dependencies entry to any module-package.yaml:
- Check the package license on PyPI (
pip show <pkg>or PyPI JSON API). - Verify the license is in the Approved column above (Section 3).
- If LGPL: document subprocess invocation in
license_allowlist.yaml. - Run
hatch run license-check— must exit 0. - Re-sign the module manifest (
hatch run sign-modules). - Run
hatch run verify-modules-signature(strict bundle frommodule-verify-policy.sh) — must pass.
7. Phase 2 tracking
| Package | Current status | Phase 2 action |
|---|---|---|
yamllint |
dev-only (GPL-3.0-or-later) | Replace with a non-GPL YAML lint path once CI / pre-commit parity is preserved |
gitpython |
runtime (CVE history) | Replace with dulwich adapter (3-file rewrite) |
8. Static license map
check_license_compliance.py uses a static license map for known module
pip_dependencies to avoid network calls. The mapping lives in
scripts/module_pip_dependencies_licenses.yaml (licenses: key, lowercase
package name → SPDX expression).
If you add a new manifest dependency that is not in the map, the gate
will fail (not warn) and flag it for review. Update
scripts/module_pip_dependencies_licenses.yaml after license review before
the manifest can be merged.