hip-0137

HIP-137: One License. Status Active. Hanzo's own standard — read this before implementing against it.

HIP-0137: One License

Abstract

Original Hanzo work is licensed MIT OR Apache-2.0. One expression, one file layout, every repository. The consumer picks whichever of the two suits them; we do not decide for them, and we do not decide it repository by repository.

Two things this document exists to stop. A fork's license is not ours to change. A license's text is not ours to edit.

Motivation

hanzoai/ml shipped a file named LICENSE that called itself the Apache License 2.0 and was not. Its body carried three alterations to operative text. The one that matters sat inside §4(d), the attribution clause:

canonical excluding those notices that do not pertain to any part of ours excluding any notices that do not pertain to any part of

One word. those refers back to the notices enumerated in the upstream NOTICE file; any refers to nothing and excludes at the reader's discretion. The altered clause lets a redistributor drop attribution we were obliged to carry forward — which is the single obligation Apache-2.0 asks of anyone, and the reason candle's authors are named in our tree at all.

Nobody set out to weaken the clause. Someone reflowed a license file. That is the whole failure mode, and it is why §3 below is absolute rather than sensible: there is no review process that reliably catches one word in eleven thousand bytes, so the rule has to be never edit the file, enforced by hash.

The estate had also drifted to four answers for one question. Original Hanzo repositories variously declared BSD-3-Clause, BSD-3-Clause OR Apache-2.0, bare Apache-2.0, bare MIT, and — worst — nothing at all. Four answers is not a convention with exceptions. It is the absence of one.

Specification

1. Original work is MIT OR Apache-2.0

Every repository of our own new code, in hanzoai, carries exactly this SPDX expression. Not one or the other. Both, with the choice belonging to whoever uses it.

in one sitting, without counsel.

termination clause. MIT has neither, and a corporate consumer's lawyer will ask for both.

consumer takes MIT for simplicity or Apache-2.0 for the patent grant.

This is the Rust ecosystem's default and it is what candle carries — our own largest upstream. Matching it means our forks of candle need no relicensing argument at all: the target and the inheritance are already the same expression.

Apache-2.0 alone is not a defect and does not require a migration; it is one half of the standard. But new work states both.

2. The file layout

LICENSE-MIT canonical MIT text, our copyright line LICENSE-APACHE canonical Apache-2.0 text, byte-identical, unmodified LICENSE the dual declaration, pointing at both NOTICE only where §5 requires it

In the manifest, whichever the language uses:

Cargo.toml license = "MIT OR Apache-2.0" package.json "license": "MIT OR Apache-2.0" pyproject.toml license = "MIT OR Apache-2.0" go.mod (no license field; the files govern)

In the README, one line. Not a section, not a table, not a restatement of the rule — one line naming the expression and linking here.

Two failures are specific to this layout and both have already happened here.

LICENSE must be the dual declaration, not a copy of one of the two texts. If LICENSE is byte-identical to LICENSE-APACHE, then a reader who opens the file the ecosystem tells them to open sees Apache-2.0 and nothing else — the MIT option is silently gone, whatever the manifest says. Check it by hash: LICENSE and LICENSE-APACHE having the same hash is the bug, not the confirmation.

LICENSE-MIT must carry a copyright holder line. MIT's operative sentence is "the above copyright notice ... shall be included in all copies." A LICENSE-MIT that opens on "Permission is hereby granted" has no above notice, so the clause points at nothing and the license conveys no attribution at all. The word "copyright" appearing twice in the body is not a holder line.

3. License text is canonical and is never edited

A license is a document with a name. Change its operative text and it is no longer that document, whatever the file is called. There is no such thing as "our Apache-2.0."

The canonical Apache-2.0 is fixed and checkable:

source https://www.apache.org/licenses/LICENSE-2.0.txt size 11358 bytes sha256 cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30 git blob d645695673349e3947e8e5ae42332d0ac3164cd7

Verify with sha256sum, cmp, or git hash-object. The copyright line goes in LICENSE-MIT and in NOTICE, never inside the Apache body — Apache-2.0 carries its ownership in the appendix and in NOTICE, not by amendment.

This applies to reflowing, re-wrapping, changing whitespace, "fixing" the appendix, and substituting our name for the placeholder. If the hash does not match, the file is not the license.

4. A fork carries its upstream's license, and cannot be relicensed

We may not relicense code we did not write. Not to MIT OR Apache-2.0, not to anything. The upstream license came with conditions and one of them is that it travels with the code.

Concretely, on any fork:

even to add us;

that "the above copyright notice and this permission notice shall be included in all copies"; AGPL and GPL require the notice be kept intact in place. Moving the line to NOTICE and cleaning the LICENSE does not satisfy either — the obligation names the license file;

Renaming a fork for our brand is expected. Restyling its license is not, and the distinction is not cosmetic. A branding pass in this estate retitled Papermark's commercial license and swapped the copyright holder over code that was 100% upstream. Nothing was gained and the grant we relied on was breached.

Ancestry is established from evidence, never from a description or a docs page: the copyright holder named in the LICENSE, the root or import commit, foreign copyright headers in the sources, upstream import paths, and GitHub's parent field. hanzo/s3 publicly described itself as a fork of MinIO under AGPL for months; it is SeaweedFS under Apache-2.0. Wrong ancestry and wrong license, published together, because the description was believed.

GitHub's fork flag is not evidence. A repository imported by pushing a clone reports fork: false and has no parent. Absence of a parent proves nothing about authorship.

Establish ancestry across every ref, not the default branch. Foreign history hides one branch away, and the default branch is the one place it reliably is not. In this estate: a repository badged BSD-3-Clause carried GPL-3.0 upstream code on two backup/ and rescue/ branches; another shows a five-commit grafted main that conceals the real 176-commit upstream lineage on master; a third's true origin sits on a master that is not an ancestor of main at all. A default-branch audit clears all three. git log --all, and list the roots — a repository with several unrelated root commits is several repositories, and they may not share a license.

5. NOTICE is an Apache-2.0 obligation, and only that

Apache-2.0 §4(d) is the only one of our licenses that requires a NOTICE file to be propagated. MIT, BSD and GPL discharge attribution by retaining the copyright line in the license file itself, per §4.

Do not invent obligations that do not exist. A NOTICE is not required on an MIT-only repository and claiming otherwise trains people to ignore the ones that are required.

That said: a NOTICE naming upstreams is good practice on any fork, regardless of license. It is the cheapest possible way to say where the code came from, and it puts the answer where the next reader will look.

6. Relicensing requires holding the copyright

You cannot relicense what you do not own. Before assuming a repository is single-origin:

git shortlog -sne <default-branch>

Then dedupe by identity, not by email string — one person with three addresses is one identity — and discount bots, which author nothing copyrightable.

Work by employees and contractors is work-for-hire and its copyright is the company's; a contributor who is not the founder is not thereby an outside party, and treating them as one blocks decisions that were never blocked. What genuinely blocks is a third-party contributor. Their contributions need their assent or a prior IP assignment. That is a paperwork question and it does not have a git answer — resolve it or leave the license alone.

The same test applies at file granularity. hanzoai/ml's hanzo-bindgen-cuda stays MIT rather than going dual, because it derives from Nicolas Patry's MIT bindgen_cuda and not from candle. One crate in thirty-one, correct on its own facts.

Where a repository mixes our code with a weak-copyleft upstream, declare the split rather than relicensing either side. EPL-2.0 §3.1 is file-level: the EPL-covered files stay EPL, our files are MIT OR Apache-2.0, NOTICE lists which paths are which. Whole-repository relicensing in either direction is wrong — one way strips a grant, the other over-corrects.

7. Already-published metadata is not retracted

A published package version's license metadata is immutable. Correct the repository, correct subsequent releases, and leave prior versions alone: the LICENSE files govern, and yanking breaks every consumer in order to fix a label. hanzoai/ml has 191 published crates.io versions carrying the previous expression and none were yanked.

The corollary is that a relicense is not finished when the repository merges. Some registries read the license from the repository at a tag, and those follow along; others bake it into the published artifact, and those keep serving the old expression until a release is cut, however correct the default branch looks. Go modules are the first kind. npm is the second — the license field ships inside the tarball. So the last step is a release, and until it happens the registry and the repository disagree in public. That disagreement is invisible precisely because the repository looks right, which is how it survives for months.

8. BSD-3-Clause is out of scope for hanzoai

BSD-3-Clause and BSD-3-Eco are not used for original Hanzo work. They remain available to luxfi and zoo, which is where the estate's BSD lineage lives. This is a scoping decision, not a judgement about BSD.

BSD-3-Clause appearing on a hanzoai repository means one of two things, and they are handled differently:

Tell them apart by the copyright holder in the LICENSE. "Copyright (c) 2015, Pierre Curto" is an upstream we forked. "Copyright (c) 2026, Hanzo AI, Inc." on a BSD-3-Clause file is ours, and is drift.

Do not pin the check to the SPDX form. A repository that never adopted SPDX carries its terms in free text — // Copyright (c) 2026, Hanzo AI, Inc. BSD-3-Clause. — and a scan for SPDX-License-Identifier:\s*BSD-3-Clause returns a clean, confident zero over all of it. That happened during this audit's own sweep: the SPDX pattern reported zero across eight repositories and twelve real BSD headers were sitting in the tree. The check is "does any tracked file name a license", then classify what it finds. A confident zero is worse than a noisy positive, because nobody runs it twice.

There is a third case, and it resolves to the second. A hanzoai repository may hold a copy of code that originated in luxfi or zoo — same estate, different entity, and those orgs are BSD by standard. Its LICENSE names Lux or Zoo as the holder, not Hanzo. That is an upstream like any other and §4 governs: leave the license alone. Being able to reach the copyright holder over lunch does not make the code ours to relicense; the holder is a different legal entity and the relicense is that entity's decision to make, deliberately, not a sweep's to assume.

Rationale

Why dual rather than picking one. Choosing for the consumer buys us nothing and costs us the consumer who needed the other. Dual is not indecision; it is declining to impose a decision that is theirs.

Why this updates HIP-0135 §3. HIP-0135 set Apache-2.0 for our own new code, and its reasoning — explicit patent grant, attribution without reciprocity — holds unchanged and is half of this rule. Adding MIT alongside costs one file and removes the friction Apache-2.0 has with MIT-only ecosystems, chiefly Rust, where we do most of our upstream work. Everything else in HIP-0135 stands: the four states, the competition test, and that public-and-unlicensed is a defect.

Why §3 is absolute. Every alteration in the hanzoai/ml license was plainly innocent. That is exactly the problem — a rule that depends on catching a bad edit fails against good-faith ones. A hash does not.

Why forks are strict. Attribution is the cheapest condition any license imposes. Stripping it saves nothing, and it is the one breach that costs the grant.

References