Skip to main content

OSPAC - Open Source Policy as Code

License Python 3.10+ PyPI version

OSPAC answers a narrow question: given a set of licenses and something you intend to do with them, is that allowed? It reads the licenses, applies a policy you keep in Git, and returns an action (approve, deny, or flag for review) along with the obligations you take on if you proceed.

The answer lives in a policy file, not in OSPAC's code. Compliance rules differ between a mobile app and an internal service, and they change as legal guidance changes, so OSPAC treats those rules as data: versioned, reviewable in a pull request, and testable.

It ships with the complete SPDX license list, so it works offline the moment it is installed. Nothing needs to be generated or downloaded first.

Features

  • Policy as Code: compliance rules in YAML or JSON, not hardcoded
  • Complete SPDX dataset: every SPDX license bundled in the wheel, no setup step
  • Distribution-aware: the same licenses can pass for internal and fail for mobile
  • Compatibility engine: per-linking-context rules for static and dynamic linking
  • Obligation tracking: derived deterministically from structured license fields
  • JSON-first output: every command defaults to JSON for scripting and MCP
  • Offline: evaluation makes no network calls and consults no model

Installation

pip install ospac

Requires Python 3.10 or later. The license dataset is included.

pip install "ospac[semcl]"   # osslili + upmex, for scanning real projects
pip install "ospac[llm]"     # LLM providers, only needed to regenerate the dataset
pip install "ospac[all]"     # both

Quick start

# Can these two licenses be combined?
ospac check GPL-2.0 Apache-2.0
# → incompatible

# Is this set acceptable for how we ship?
ospac evaluate -l "GPL-3.0,MIT" -d commercial -o text
# → deny

# What do we owe if we ship MIT code?
ospac obligations -l MIT -f checklist
# → ☐ Retain copyright notices
#   ☐ Include license text

# Start a policy of our own
ospac policy init --template mobile --output mobile_policy.yaml

The distribution type is what makes the same input produce different answers. -d mobile is stricter than -d internal because the policy says so, not because OSPAC hard-codes it.

How it works

OSPAC has two halves that are worth keeping separate in your head.

The tool you install is offline and deterministic. It reads the bundled dataset and your policy files, and returns a decision. No network, no model.

The dataset behind it is regenerated from upstream SPDX by an automated pipeline that runs monthly, analyses new license texts with an LLM, validates the result, and opens a pull request for human review. Obligations are derived mechanically from structured boolean fields rather than written by the model, and a correction table pins fields that LLMs consistently got wrong. Dataset updates reach you as ordinary patch releases.

You never run the generation pipeline to use OSPAC. See The dataset for how it works and how to run it yourself.

Two things worth knowing early

There is always a policy in play. With no --policy-dir, OSPAC loads a bundled default enterprise policy and says so on stderr. That default is opinionated: it denies GPL for commercial distribution and flags LGPL static linking for review. Treat it as a starting point to copy, not as neutral ground.

Exit codes do not reflect the decision. ospac evaluate and ospac check exit 0 even when the answer is deny. Parse the JSON in CI:

ACTION=$(ospac evaluate -l "$LICENSES" -d mobile | jq -r '.result.action')
[ "$ACTION" = "deny" ] && exit 1

See Integration for a CI gate that cannot pass by accident.

Python API

from ospac import PolicyRuntime
from ospac.models.compliance import ActionType

runtime = PolicyRuntime("./compliance-policy.yaml")
assert not runtime._using_default, "policy failed to load"

licenses = ["MIT", "Apache-2.0", "GPL-3.0"]
result = runtime.evaluate({
    "licenses": licenses,
    "licenses_found": licenses,
    "distribution_type": "mobile",
    "distribution": "mobile",
    "context": "general",
    "linking_type": None,
})

if result.action == ActionType.DENY:
    raise SystemExit(result.to_dict()["remediation"])

Full reference at Python API.

Documentation

Full documentation is at semclone.github.io/ospac.

  • Overview: what OSPAC does, installing, first run
  • Commands: every CLI command and flag, with real output
  • Policies: rule schema, how matching works, templates
  • The dataset: how license data is shaped, shipped, and regenerated
  • Python API: using OSPAC as a library
  • Integration: CI, the SEMCL.ONE toolchain, MCP

SEMCL.ONE toolchain

OSPAC evaluates licenses but does not discover them. Finding out what is in a project is the job of the neighbouring tools:

Tool Role
osslili Detects licenses and copyright in source trees
upmex Extracts declared metadata from package files
ospac Decides whether the result is acceptable under policy

They compose over JSON on the command line:

LICENSES=$(upmex extract gson-2.10.1.jar \
  | jq -r '[.licensing.declared_licenses[].spdx_id] | join(",")')

ospac evaluate -l "$LICENSES" -d mobile

Contributing

See CONTRIBUTING.md. Documentation sources live in docs/ and are published by GitHub Pages; every page has an "Edit this page on GitHub" link.

Support

License

This project is dual-licensed, and the split matters.

Software code: Apache-2.0. All source in this repository (Python, scripts, configuration). Commercial use, modification, and distribution are permitted. See LICENSE.

License database: CC BY-NC-SA 4.0. The dataset in ospac/data/ is Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International: non-commercial use only, attribution required, derivatives shared alike. See DATA_LICENSE.

Installing OSPAC and running it inside a commercial organization to check your own compliance is ordinary internal use. Redistributing the dataset, or building a commercial product on top of it, is what the NonCommercial term restricts. If you are unsure which side of that line you are on, that is a question for your counsel.

Authors

See AUTHORS.md.

Acknowledgments

  • SPDX Project for license standardization
  • SEMCL.ONE ecosystem for integration capabilities
  • OpenChain Project for compliance best practices

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ospac-1.3.0.tar.gz (3.9 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ospac-1.3.0-py3-none-any.whl (4.7 MB view details)

Uploaded Python 3

File details

Details for the file ospac-1.3.0.tar.gz.

File metadata

  • Download URL: ospac-1.3.0.tar.gz
  • Upload date:
  • Size: 3.9 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for ospac-1.3.0.tar.gz
Algorithm Hash digest
SHA256 034e1d0d16c1487b0823c3892d00a23f1eb30836d2099b01be741999a35851cb
MD5 d9d1d4e41856553591447c4076a4df26
BLAKE2b-256 cb7a3895381261518c1244a4db3910fcea11e549f683f8940e655b614e496f5c

See more details on using hashes here.

Provenance

The following attestation bundles were made for ospac-1.3.0.tar.gz:

Publisher: python-publish.yml on SemClone/ospac

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ospac-1.3.0-py3-none-any.whl.

File metadata

  • Download URL: ospac-1.3.0-py3-none-any.whl
  • Upload date:
  • Size: 4.7 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for ospac-1.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 12f2fadec6397a774f84c653a2e19faacaf0c2e0fec1f1d02579cf566d74f963
MD5 2e2c12be69a59ae07fb181e4315ef2d9
BLAKE2b-256 c9f4a4b9bd062a75815c3a6254b574dc526f4a17a1ad76b67494936d233a1b68

See more details on using hashes here.

Provenance

The following attestation bundles were made for ospac-1.3.0-py3-none-any.whl:

Publisher: python-publish.yml on SemClone/ospac

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page