Skip to content

Declare the Unlicense everywhere, matching LICENSE - #198

Open
MaxGhenis wants to merge 1 commit into
mainfrom
unlicense-metadata
Open

MaxGhenis wants to merge 1 commit into
mainfrom
unlicense-metadata

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

The mismatch

GitHub detects LICENSE as the Unlicense (gh api repos/PolicyEngine/policybench/license --jq .license.spdx_id returns Unlicense), but pyproject.toml declared license = "MIT" and docs/myst.yml declared license: MIT. A wheel built from main carries both at once: its metadata says License-Expression: MIT while the LICENSE file bundled with it is the Unlicense text.

This PR declares the Unlicense everywhere.

Why the Unlicense

The history shows the Unlicense was selected when the repository was created, and MIT arrived with the first code scaffold:

When Commit What it set
2025-02-15 19:53 ET df6e5dd "Initial commit" The repository was created on GitHub with the Unlicense selected. The commit, committed by GitHub <noreply@github.com>, adds only LICENSE, a .gitignore and a two-line README.md.
2025-02-15 21:02 ET d1a6630 "Initial version" The first code scaffold, committed in Max's fork 69 minutes later and merged as #1 on 2025-02-17, added license = {text = "MIT"} and the classifier License :: OSI Approved :: MIT License to pyproject.toml. The same commit also checked in stray renders (tmp9wmiy2qk.html, output.html).
2026-02-16 87fb46a "PolicyBench v2: complete rewrite" Dropped the MIT classifier; the license line carried over unchanged.
2026-02-20 2e0aa0d docs/myst.yml was added with license: MIT.
2026-05-05 4d96c18 "Address review findings" Rewrote {text = "MIT"} as the SPDX string "MIT". The value did not change.

No commit on any branch changed LICENSE, and no commit message, PR or issue discusses choosing MIT. The Unlicense also matches how Max set up repositories at the time: 12 of the 15 public repositories he created under his own account from October 2024 to April 2025 use it (two use MIT, one AGPL-3.0).

It also imposes fewer conditions. The Unlicense dedicates the authors' copyright to the public domain where the law allows it and lets anyone use the code for any purpose, with no conditions on reuse. MIT requires that its copyright and permission notice be included in all copies or substantial portions. Both disclaim warranties.

Relation to the September 2026 MIT work. In September Max chose MIT over Apache-2.0 for Axiom's public code because it is the more permissive of the two (TheAxiomFoundation/axiom-mcp#25). In the same month, a license pass across PolicyEngine repositories added MIT to unlicensed code repositories. That pass left the repositories already under the Unlicense, this one included, as they were. The more-permissive reasoning points to keeping the Unlicense here. Moving to MIT later would mean replacing LICENSE and the three declarations; tests/test_license.py would point at each one.

Dependencies. PolicyBench imports policyengine, policyengine-us and policyengine-uk, which are AGPL-3.0. The FSF's license list lists both the Unlicense and Expat/MIT as compatible with the GNU GPL, so this change does not alter how those dependencies apply.

Contributors. On main, every commit is authored by Max except Pavel Makarchuk's Google Analytics tag (#79). The squash-merged #5 and #47 include commits by Daphne Hansell, and #82 includes one by Pavel; each is credited in a Co-authored-by trailer. All of it landed while LICENSE held the Unlicense (and pyproject.toml said MIT).

The tradeoff

  • "Any OSI-approved license" rules accept the Unlicense. It is OSI-approved: the SPDX license list (3.29.0) marks it isOsiApproved: true, and OSI's page https://opensource.org/license/unlicense lists its approval. CC0, by contrast, is not OSI-approved; Creative Commons withdrew it from OSI review.
  • Rules that name an allowlist can still reject it. For example, one 2026 civic-tech challenge's official rules accept only Apache-2.0, MIT, BSD-3-Clause and BSD-2-Clause for first-party code. They name "public-domain dedications (e.g., CC0, the Unlicense)" as excluded. MIT passes a rule like that; the Unlicense does not.
  • If that ever matters, PolicyBench could be relicensed to MIT, or offered as Unlicense OR MIT as ripgrep is. The dual option needs an MIT license file with a copyright line. Whether a dual offer satisfies an exclusive allowlist is the rule-setter's call.
  • No published release is affected. PolicyBench has never been on PyPI (https://pypi.org/pypi/policybench/json returns 404), so no released distribution carries the MIT metadata.

Changes

  • pyproject.toml: license = "Unlicense". The wheel and sdist built with uv build now carry License-Expression: Unlicense and License-File: LICENSE (Metadata-Version 2.4).
  • docs/myst.yml: license: Unlicense. MyST reads a single string as the docs site's license, and its license table knows the Unlicense id (myst-frontmatter 1.10.1, and the copy bundled with jupyter-book 2.1.2, which uv.lock pins). No CI workflow builds the MyST site, so the new test is what checks this file.
  • README.md: new "License" section naming the Unlicense. It also says that third-party files keep their own licenses and terms: the vendored Quarto web libraries in app/public/paper/web/site_libs/ and the captured Vals.ai leaderboard in paper/external/. The README had no license badge or section before.
  • tests/test_license.py: new; described under Invariant.
  • Unchanged: LICENSE (GitHub already detects it correctly); app/package.json ("private": true, no license field); paper/_quarto.yml (no license field); MANIFEST.in (already includes LICENSE); uv.lock (uv lock --check passes; the lock does not record the project's license). The site and the data-release notes declare no license.

Invariant

Every license declaration in the repository names the license in LICENSE, SPDX id Unlicense. tests/test_license.py checks it three ways:

  1. LICENSE is the Unlicense text.
  2. Every tracked file whose format carries a license field names the Unlicense at every license key, at any depth. Those formats are pyproject.toml, setup.cfg, package.json, myst.yml, _quarto.yml, CITATION.cff, codemeta.json, .zenodo.json and Cargo.toml. Files are found with git ls-files, and the vendored site_libs are excluded. The test also requires pyproject.toml and docs/myst.yml to be among the files found, and pyproject.toml to carry no License :: classifier.
  3. The README's License section names no other license, and no static shields.io license badge names one.

Mutation checks, run in a scratch worktree:

Mutation Result
main's three files 2 failed, 1 passed (LICENSE itself is unchanged)
add CITATION.cff with license: MIT 1 failed
add CITATION.cff with license: Unlicense all pass
app/package.json gains "license": "MIT" 1 failed
MIT classifier in pyproject.toml 1 failed
pyproject.toml license Unlicense OR MIT 1 failed
pyproject.toml legacy {text = "Unlicense"} all pass
license-files = ["LICEN[CS]E*"] all pass
MyST license: "Unlicense" (quoted) all pass
MyST {content: Unlicense, code: Unlicense} all pass
MyST {content: CC-BY-4.0, code: Unlicense} 1 failed
README MIT shields badge 1 failed
README License section mentions MIT 1 failed
README License section rewrapped all pass

No computed output changes, so property-based tests do not apply.

Verification

  • uv run ruff check . and uv run ruff format --check . pass.
  • uv run pytest -m "not slow" on Python 3.12 at head 89f16e0: 1566 passed, 8 skipped, 15 deselected.
  • uv build: the wheel and sdist metadata are as stated above. On main, the same build gives License-Expression: MIT with the Unlicense text as the bundled LICENSE.
  • The Paper workflow runs on this PR because pyproject.toml changed; the license value does not enter the render.

🤖 Generated with Claude Code

LICENSE has held the Unlicense since the repository was created
(df6e5dd), but pyproject.toml declared MIT from the first code scaffold
(d1a6630) and docs/myst.yml (2e0aa0d) repeated it, so a built wheel said
License-Expression: MIT while bundling the Unlicense text.

Set the package metadata and the MyST docs license to Unlicense, add a
README License section that names it and exempts the vendored
third-party files, and add tests/test_license.py, which finds every
tracked license-bearing manifest and checks that each names the
Unlicense, that pyproject carries no License classifier, and that the
README names no other license.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
policybench-site Ready Ready Preview Oct 6, 2026 11:07am UTC

Request Review

This branch was successfully deployed

1 active deployment
Preview — 89f16e04 Deployed Oct 6, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant