fix(legal): the package declared MIT while shipping the Apache-2.0 text (LEGAL-001)

Merged
YA
2mo ago
+1779-4

LEGAL-001 — license truth for @wave-av/cli

The defect

package.json on origin/main declared "license": "MIT". The LICENSE file beside it is the Apache-2.0 text, and has been since 5da8018 — "chore: adopt Apache-2.0 license + add NOTICE… Standardize the open WAVE protocol/SDK surface on Apache-2.0 (patent grant for adoption). Replaces any prior license." The README's License section also said MIT. Three declarations, one file, two different licenses.

Reproduction against origin/main before this change:

$ git show origin/main:package.json | python3 -c "import json,sys;print(json.load(sys.stdin)['license'])"
MIT
$ git show origin/main:LICENSE | head -1
                                 Apache License
$ grep -A2 '^## License' README.md
## License

MIT

Second defect, found while building the gate. npm always includes LICENSE in a tarball regardless of the files array — but never NOTICE. files listed only dist, templates, README.md, CHANGELOG.md:

$ npm pack --dry-run --json --ignore-scripts | jq -r '.[0].files[].path' | grep -iE 'licen|notice'
LICENSE

So the NOTICE reserving the WAVE trademarks — which Apache-2.0 §4(d) requires redistributions to carry — has never been in a published tarball. The same is true of every other WAVE npm package (see the NOTICE column in LICENSE-LEDGER.md); this PR fixes it here.

What the published artifact actually is

The brief this lane started from said @wave-av/cli@1.0.8's tarball contains Apache-2.0 text under MIT metadata. It does not — the published 1.0.8 tarball is internally consistent, MIT metadata with the MIT text:

$ curl -sL https://registry.npmjs.org/@wave-av/cli/-/cli-1.0.8.tgz | tar -xz
$ head -1 package/LICENSE ; jq -r .license package/package.json
MIT License
MIT

The reason is a date, not a packaging bug: 1.0.8 was published 2026-04-03 (registry.npmjs.org time field), two months before the 2026-06-04 Apache-2.0 adoption commit. The [1.0.8] CHANGELOG entry claims "License changed to Apache-2.0, replacing MIT" — true of the repository, never true of the release. Apache-2.0 has never been published for this package. The history is left as written; a correction note is added under [Unreleased].

The fix

  • package.json — "license" → Apache-2.0; "LICENSE" and "NOTICE" added to files.
  • README.md — License section now names Apache-2.0 and points at both files.
  • package-lock.json — root license regenerated via npm install --package-lock-only (one line).
  • CHANGELOG.md — records both fixes and corrects the false [1.0.8] license claim.

The gate that keeps it fixed

npm run license:check (new, offline, wired into CI as license-truth / local-truth). It reads the license text and names it, then requires every declaration to match — because you cannot catch this class of defect by comparing two declarations to each other. Rules: license-file-present, license-file-identifiable, declared-license-present, declared-matches-text, lockfile-matches-manifest, readme-matches-manifest, license-shipped, notice-shipped, no-strong-copyleft-runtime.

It reads only repo files, so it is deterministic and cannot go red because a registry is having a bad afternoon. Registry reconciliation is a separate job (registry-drift) on a weekly schedule + workflow_dispatch.

npm run license:ledger regenerates LICENSE-LEDGER.md: it downloads every published WAVE npm tarball and PyPI wheel, reads the LICENSE inside the artifact, and compares that against what each source repo declares today. tar.gz and zip are parsed in pure Node (scripts/lib/archive.mjs) so the ledger needs no tar/unzip binary and no new dependency.

Proving runs

# on the pre-fix tree — the gate catches exactly the two real defects
$ node scripts/license-truth.mjs check ; echo EXIT=$?
2 license contradiction(s):
  [declared-matches-text] package.json declares "MIT" but the LICENSE file is the Apache-2.0 text
  [notice-shipped] the repo has a NOTICE file and is Apache-2.0, but NOTICE is not in the packed tarball
EXIT=1

# after the fix
$ npm run license:check ; echo EXIT=$?
OK — every license surface agrees.
EXIT=0

$ npm test
Test Files  6 passed (6)      Tests  65 passed (65)

$ npm run build
ESM ⚡️ Build success in 75ms

54 of those 65 tests are new. The load-bearing one builds a fixture repo declaring MIT next to Apache-2.0 text and asserts declared-matches-text fires — if that ever stops failing, the gate has stopped working. Writing the tests found a real bug in my own classifier: classifyCopyleft("LGPL-3.0-or-later") recursed until the stack blew, because \bOR\b matched the -or- inside the identifier. Fixed by requiring whitespace-delimited OR; the test that caught it is kept.

What this PR does NOT touch

.github/workflows/release.yml (owned by #17/#45) and src/lib/version.ts (owned by #46) are untouched — no file in this diff appears in any open PR.

Drift this PR reports but cannot fix from here

LICENSE-LEDGER.md names four contradictions in other repos, each with a receipt. They need their own PRs:

packagepublishedsource declaresrepo LICENSE file
@wave-av/workflow-sdk@1.0.6MITApache-2.0sdk-typescript/packages/workflow-sdk/LICENSE is MIT
wave-av-sdk@2.0.0 (PyPI)MITApache-2.0sdk-python/LICENSE is MIT
wave-sdk@2.0.0 (PyPI)MITMITroot LICENSE is Apache-2.0 (being fixed in sdk-python#44)
@wave-av/create-app@1.0.9MITunresolvedno package.json on any wave-av default branch — source unknown

The ledger deliberately renders that last row as unverified, not "consistent": an artifact that agrees with itself and has never been checked against a source repo is not a pass.

Note the @wave-av/cli row will read DRIFT until this merges — the ledger reads HEAD of wave-av/cli, which still declares MIT. Regenerate after merge.

Rollback

Revert the commit. The gate is additive: license-truth.yml is a new workflow (no existing workflow is modified), scripts/license-truth.mjs and scripts/lib/* are new files with no importers in src/, and the two npm scripts are new keys. Reverting restores the MIT declarations and removes the gate; nothing else in the build or the published surface changes. No republish is implied by this PR — the npm metadata for 1.0.8 is immutable, and any republish is an operator decision.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


[!NOTE]
Medium Risk
Changes legal metadata and published package contents (LICENSE/NOTICE shipping) and adds CI that can block merges on license drift; runtime CLI code is untouched.

Overview
Fixes LEGAL-001: the repo declared MIT in package.json, README, and lockfile while LICENSE was already Apache-2.0, and NOTICE was never included in the npm tarball (now added to files alongside LICENSE). User-facing license text in the README is updated to Apache-2.0 with pointers to LICENSE and NOTICE.

Adds an offline npm run license:check gate that compares declared licenses to LICENSE file text, checks README/lockfile alignment, verifies packed tarball contents, and blocks strong copyleft in runtime deps. npm run license:ledger downloads published WAVE npm/PyPI artifacts, parses tarballs/wheels in pure Node, and writes LICENSE-LEDGER.md for cross-repo drift. New license-truth GitHub Actions workflow runs the offline check on PRs/pushes and scheduled registry reconciliation separately.

Includes broad Vitest coverage for the gate, archive readers, and ledger rendering; changelog documents the fix and corrects the mistaken [1.0.8] “published as Apache-2.0” note.

Reviewed by Cursor Bugbot for commit 32a1b65d6b754361881f2ef59f42e41482cfc365. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by Sourcery

Standardize the package’s license surfaces on Apache-2.0 and add automated checks that prevent license metadata, text, and published artifacts from drifting apart.

New Features:

  • Add offline license verification and registry reconciliation commands for validating declared licenses, license text, package contents, dependency copyleft, and published artifacts.

Bug Fixes:

  • Align the package metadata, lockfile, README, and published file list with the Apache-2.0 license and ensure the required NOTICE file is included in npm packages.
  • Correct the changelog’s claim about the license of the previously published 1.0.8 release.

Enhancements:

  • Add pure Node archive parsing and license classification utilities for auditing npm and PyPI artifacts.
  • Prevent copyleft classification recursion for hyphenated SPDX identifiers.

CI:

  • Add a CI workflow that runs deterministic local license checks on pull requests and pushes, with scheduled and manually triggered registry drift reconciliation.

Documentation:

  • Document the Apache-2.0 license and NOTICE requirements in the README and changelog.
  • Add a generated license ledger comparing published WAVE artifacts with their source repositories.

Tests:

  • Add comprehensive tests covering license consistency rules, artifact inspection, archive parsing, dependency classification, URL validation, and ledger rendering.