fix(legal): 52 packages declared Apache-2.0 and shipped the MIT text (LEGAL-001) - #80
Conversation
…(LEGAL-001) Every `sdk-typescript/packages/*/package.json` declares "license": "Apache-2.0". The LICENSE file sitting beside each of them was the MIT text. Same for sdk-python (pyproject declares Apache-2.0, sdk-python/LICENSE was MIT) and for the two Rust crates that declare Apache-2.0 but resolved to the MIT sdk-rust/LICENSE. The repository ROOT LICENSE has been Apache-2.0 the whole time, which is exactly why this went unseen: every existing check looks at the root, while npm, pip and cargo pack the LICENSE from the PACKAGE directory. The consequence is already public — @wave-av/workflow-sdk@1.0.6 is on npm carrying the MIT text from a source tree that declares Apache-2.0. This changes no declaration. Each LICENSE file is rewritten to the license its own manifest ALREADY declares: - 49 sdk-typescript packages -> Apache-2.0 (manifests already said so) - sdk-python -> Apache-2.0 (pyproject already said so) - sdk-rust/wave, wave-core -> Apache-2.0 (Cargo.toml already said so) - sdk-rust/wave-x402 -> MIT (Cargo.toml says MIT) sdk-go, sdk-ruby and sdk-rust/LICENSE are untouched: they are internally consistent at MIT, and moving them to Apache-2.0 would be a relicensing decision, which is not a CI fix. Added scripts/license-consistency.mjs, which reads the license TEXT beside each manifest, names it, and fails when it does not match the declaration. Run against origin/main it reports 52 failures out of 54 packages; against this branch, zero. Wired in as the `license-consistency / licenses` check. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Your free trial PR review limit of 300 PRs has been reached. Please upgrade your plan to continue using CodeAnt AI. |
|
ⓘ Qodo reviews are paused because your workspace is out of credits. Ask your workspace admin to add credits to resume reviews. Manage billing |
|
Warning Review limit reachedNext included review available in 3 minutes. View limit detailsLimit details: You’ve used the included review currently available. Your 91 included PR review attempts over the past 7 days set your current allowance at 1 review per hour. Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab. Review configuration: ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Team Run ID: 📒 Files selected for processing (55)
Comment |
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_c1fba266-16da-4540-a822-08b894a5ade6) |
There was a problem hiding this comment.
Sorry @yakimoto, this account has used its review budget of 2,500,000 diff characters for the last 7 days.
You can request another review in 17 hours and 44 minutes by commenting @sourcery-ai review.
|
This PR has 11,773 reviewable changed lines after ignored/generated files are excluded, above this repository's 5,000-changed-line automatic review limit. Most of the diff comes from:
Comment |
Reviewer's GuideAligns all contradictory publishable package LICENSE files with their existing manifest declarations and introduces a dependency-free repository-wide audit enforced on pull requests and main, while intentionally preserving consistent MIT licensing and leaving NOTICE propagation for a follow-up. Flow diagram for license consistency enforcementflowchart LR
M[Publishable manifests] --> D[declaredLicense]
M --> N[nearestLicense]
N --> T[detectSpdxFromText]
D --> C{declared matches shipped?}
T --> C
C -->|yes| O[CI passes]
C -->|no| F[CI fails with mismatch]
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — The changes correct license text shipped by 53 packages and add a read-only CI consistency audit, without modifying normal request-path code. Human review remains warranted because the diff changes an artifact under the billing package and all changed files are outside the author’s designated ownership domain. Not approved because:
Review your spending limits in Billing settings. You can add or adjust custom eligibility rules. Learn more. |
| export function declaredLicense(manifestPath) { | ||
| const text = readFileSync(manifestPath, 'utf8'); | ||
| if (manifestPath.endsWith('.json')) { | ||
| const pkg = JSON.parse(text); | ||
| if (pkg.private === true) return null; // never published | ||
| const l = pkg.license; | ||
| return typeof l === 'string' ? l : l?.type ?? null; | ||
| } |
There was a problem hiding this comment.
💡 Edge Case: declaredLicense doesn't handle SPDX compound expressions in package.json
declaredLicense reads pkg.license as a plain string or {type} object, but npm also allows SPDX expressions like (MIT OR Apache-2.0) and the deprecated licenses: [...] array form. If any manifest is later authored with a compound expression, detectSpdxFromText will never return a matching compound string, so the gate will permanently fail that package even though it's technically valid — a false positive that could block a legitimate release. Not triggered today since all current manifests use plain SPDX-string licenses, but worth a comment or explicit handling (e.g., treat non-simple-identifier strings as unsupported and skip with a warning rather than silently mismatching).
Was this helpful? React with 👍 / 👎
| if (manifestPath.endsWith('Cargo.toml')) { | ||
| // Only a crate's own [package] license counts; a workspace Cargo.toml has none. | ||
| return text.match(/^\s*license\s*=\s*["']([^"']+)["']/m)?.[1] ?? null; | ||
| } |
There was a problem hiding this comment.
💡 Edge Case: Cargo.toml license regex is not scoped to the [package] table
declaredLicense for Cargo.toml matches /^\s*license\s*=\s*["']([^"']+)["']/m against the whole file, so if any future dependency/workspace metadata table also defines a top-level license = "..." key before [package], or a crate is restructured, the regex could grab the wrong value. Current crates (sdk-rust/wave, wave-core, wave-x402) are unaffected since their only license key is under [package], but scoping the match to text between [package] and the next [ header would make the gate robust against future Cargo.toml changes.
Was this helpful? React with 👍 / 👎
| export function detectSpdxFromText(text) { | ||
| if (typeof text !== 'string' || !text.trim()) return 'UNKNOWN'; | ||
| if (/apache\s+license\s*\n?\s*version\s+2\.0/i.test(text)) return 'Apache-2.0'; | ||
| if (/mozilla public license\s*,?\s*(version\s+)?2\.0/i.test(text)) return 'MPL-2.0'; | ||
| if (/\bbsd\b.*\blicense\b/i.test(text) && /neither the name of/i.test(text)) return 'BSD-3-Clause'; | ||
| if (/permission to use, copy, modify,? and\/or distribute/i.test(text)) return 'ISC'; | ||
| if (/\bmit license\b/i.test(text)) return 'MIT'; | ||
| if (/permission is hereby granted, free of charge/i.test(text)) return 'MIT'; | ||
| return 'UNKNOWN'; | ||
| } | ||
|
|
||
| /** The license a manifest declares, or null when it declares none. */ | ||
| export function declaredLicense(manifestPath) { | ||
| const text = readFileSync(manifestPath, 'utf8'); | ||
| if (manifestPath.endsWith('.json')) { |
There was a problem hiding this comment.
💡 Quality: New license-consistency script has no unit tests
The script exports pure, easily-testable functions (detectSpdxFromText, declaredLicense, nearestLicense, audit) but ships with no automated test file — only manual "proving run" transcripts in the PR description. Since this is meant to be a required, long-lived CI gate, a small test suite (e.g. asserting detectSpdxFromText against known Apache/MIT/BSD/ISC sample texts, and nearestLicense ancestor fallback behavior) would prevent silent regressions when the script is later modified.
Was this helpful? React with 👍 / 👎
|
Note Automatic reviews are paused because your team has used its included automatic processing for this billing period (headroom scales with your seat count). You can still comment "Gitar review" to run one anytime, and automatic reviews resume on their own by October 1. Add seats for more headroom. Code Review 👍 Approved with suggestions 0 resolved / 3 findingsFixes 52 packages that declared Apache-2.0 but shipped MIT license text, and adds 💡 Edge Case: declaredLicense doesn't handle SPDX compound expressions in package.json📄 scripts/license-consistency.mjs:43-50
💡 Edge Case: Cargo.toml license regex is not scoped to the [package] table📄 scripts/license-consistency.mjs:61-64
💡 Quality: New license-consistency script has no unit tests📄 scripts/license-consistency.mjs:31-45 The script exports pure, easily-testable functions ( 🤖 Prompt for agentsOptionsDisplay: compact → Showing less information. Comment with these commands to change the behavior for this request:
Was this helpful? React with 👍 / 👎 | Gitar |
LEGAL-001 — 52 packages declared Apache-2.0 and shipped the MIT text
The defect
Every
sdk-typescript/packages/*/package.jsonin this repo declares"license": "Apache-2.0". TheLICENSEfile sitting beside each of them was the MIT text. Same forsdk-pythonand for the two Rust crates that declare Apache-2.0.That pattern held for all 49 TypeScript packages:
Why nobody saw it. The repository root
LICENSEhas been Apache-2.0 the whole time, and every existing check looks at the root. But npm, pip and cargo pack theLICENSEfrom the package directory, so the root file is not the one that ships.It is already public.
@wave-av/workflow-sdk@1.0.6is on npm right now carrying the MIT text, published from a source tree that declares Apache-2.0:Every other
sdk-typescriptpackage is unpublished from this repo today. Publishing any of them before this lands means shipping 49 more packages whose metadata and license file disagree.What this PR changes — and deliberately does not
No declaration is changed. Each
LICENSEfile is rewritten to the license its own manifest already declares:sdk-typescript/packages/*sdk-pythonsdk-rust/wave,sdk-rust/wave-coresdk-rust/LICENSE)sdk-rust/wave-x402Untouched on purpose:
sdk-go,sdk-rubyandsdk-rust/LICENSE. Those are internally consistent at MIT —sdk-ruby/wave-sdk.gemspechasspec.license = "MIT",sdk-go's README says "License: MIT", and neither has a contradiction to fix. Moving a consistent package from MIT to Apache-2.0 is a relicensing decision, and that belongs to a human, not to a lane closing a CI gap. They are listed under "operator decision" below.The gate
scripts/license-consistency.mjs(new) walks every publishable manifest —package.json,pyproject.toml,Cargo.toml,*.gemspec— finds theLICENSEthat would actually travel with it (own directory, else nearest ancestor: the same lookup the packagers perform), reads the license text and names it, and fails when the declaration does not match. Comparing declarations to one another cannot catch this class of defect; only reading the file can. It skips"private": truemanifests and any manifest that declares no license.It is dependency-free on purpose: this repo has no root
package.json, so the gate has to run against a bare checkout with nothing installed. Wired in aslicense-consistency / licenseson every PR and onmain.Proving runs
Red on
origin/main— a detached worktree atorigin/mainwith only the script copied in:Green on this branch:
The two packages that pass on both sides are
sdk-rubyandsdk-rust/wave-x402— the consistent-MIT ones this PR leaves alone. That is the gate proving it distinguishes contradiction from a license you may not like.Contention
No open PR in this repo touches any
LICENSEfile. #51 touchessdk-typescript/package.jsonand #50 touchessdk-typescript/packages/mcp-server/package.json; neither path appears in this diff. Verified withgh pr view <n> --json filesacross all 26 open PRs.Known gap this PR does not close
Apache-2.0 §4(d) requires redistributions to carry the
NOTICEfile. This repo has a rootNOTICE, but npm includesLICENSEin a tarball automatically and neverNOTICE— so no published WAVE package carries one (confirmed for all six npm packages and both PyPI wheels). Closing that means a per-packageNOTICEplus afilesentry in 49 manifests, which would bury this correctness fix in an unrelated 100-file diff. It is filed as a follow-up.Rollback
Revert the commit. The change is 53
LICENSEfiles plus one new script and one new workflow; no source, build, manifest, or lockfile is touched, so nothing that compiles or publishes changes behaviour. Reverting restores the MIT text and removes the gate. This PR implies no republish — npm metadata for already-published versions is immutable, and any republish is an operator decision.🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.Note
Medium Risk
Changes legal license text shipped with published SDK artifacts (compliance fix, not runtime code), with broad monorepo LICENSE churn that must stay aligned with unchanged manifest declarations.
Overview
Fixes a mismatch where many publishable packages declared Apache-2.0 in manifests but shipped MIT in per-package
LICENSEfiles (what npm/pip/cargo actually pack). This PR replaces thoseLICENSEfiles with Apache-2.0 text forsdk-python, the TypeScript SDK packages shown in the diff, andsdk-rust/wave/wave-core, and adds an explicit MITLICENSEforsdk-rust/wave-x402where the crate already declares MIT.Adds
scripts/license-consistency.mjs, a dependency-free checker that walks publishable manifests (package.json,pyproject.toml,Cargo.toml, gemspecs), resolves the nearestLICENSElike packagers do, infers SPDX from the file text, and fails when it does not match the declaration.license-consistencyGitHub Actions workflow runs that script on every PR and onmain.Reviewed by Cursor Bugbot for commit 92d936b. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by Sourcery
Correct shipped package licensing and enforce consistency between package declarations and distributed license text.
Bug Fixes:
Enhancements:
CI:
Chores: