build: make LICENSE the authority for non-npm third-party notices - #3325
Draft
Astro-Han wants to merge 3 commits into
Draft
build: make LICENSE the authority for non-npm third-party notices#3325Astro-Han wants to merge 3 commits into
Astro-Han wants to merge 3 commits into
Conversation
Astro-Han
force-pushed
the
chore/models-dev-refresh-catalog-snapshot
branch
3 times, most recently
from
August 20, 2026 14:44
31a9326 to
8aa2935
Compare
Astro-Han
force-pushed
the
docs/3270-third-party-attribution
branch
2 times, most recently
from
August 20, 2026 17:43
3eab973 to
e4bdb38
Compare
Astro-Han
force-pushed
the
chore/models-dev-refresh-catalog-snapshot
branch
from
August 20, 2026 17:48
7e64456 to
8dca808
Compare
Astro-Han
force-pushed
the
docs/3270-third-party-attribution
branch
from
August 20, 2026 17:49
e4bdb38 to
78dbe2c
Compare
Base automatically changed from
chore/models-dev-refresh-catalog-snapshot
to
main
August 20, 2026 18:34
Astro-Han
force-pushed
the
docs/3270-third-party-attribution
branch
from
August 20, 2026 18:35
78dbe2c to
c34f687
Compare
…tice `@ai-sdk/provider-utils` ships no license file, so the generator substitutes the root LICENSE as its Apache-2.0 text. It substituted the whole file. LICENSE also carries Maka's THIRD-PARTY COMPONENTS section, so 140 lines describing trycua/cua and the bundled fonts were filed under one npm package in both release notices — attributing material to a party that has nothing to do with it, in the artifact whose only job is to attribute correctly. Truncate at the section header. `split` returns the whole string when the delimiter is absent, which would silently reinstate this exact bug and leave `--check` demanding that the wrong output be committed, so the delimiter is required rather than assumed. The regression test comes from PR #2907, which found this defect and carries the same fix. It deliberately matches the package without its version, so a routine dependency bump does not fail it. Both are repeated here because this branch adds LICENSE entries, and without the fix those entries would ship inside the provider-utils notice. Whichever lands second drops the duplicate. The test is wired into `check:release` and into the CI planner's CLI-package inputs, so it runs on pull requests that touch it rather than only at release preparation. Generated-by: Claude Code
The code origin audit (#2907) found two pieces of third-party material that ship in every release and appear in none of Maka's attribution surfaces, both for the same structural reason: neither is an npm package, and the notice generator walks npm production dependency trees only. `tool-output.ts` and `edit-replace.ts` already said in prose that they adapt opencode, and the adaptation is real — the escape-normalizing replacer's regular expression and its nine branches are carried over unchanged. Prose is not the notice MIT requires. Each file now records source, revision, license, and copyright, and LICENSE carries the permission notice so it travels with the release. Both files are mixed-origin, so the notice is scoped to the adapted material and neither gets a whole-file SPDX identifier. The LICENSE entry records the whole upstream chain rather than stopping at the nearest one. opencode's edit.ts credits cline diff-apply and the gemini-cli editCorrector; that file is Copyright 2025 Google LLC under Apache-2.0, and its escape set shares its first eight branches, in order, with the replacer here. opencode added the ninth and changed the pattern. Whether that shared sequence carries protected expression is deliberately not adjudicated in LICENSE — it is recorded so a licensing review can see the chain and decide. The models.dev entry states what is copied instead of characterising it. The earlier draft said the individual records were uncopyrightable facts and that what came from upstream was the selection and arrangement. Both halves were wrong: `sync-model-metadata.mjs` copies `model.name` and `model.description` verbatim, and the snapshot carries 1779 upstream-authored descriptions; while the selection and the ordering are the generator's, not upstream's. The entry now lists the fields actually carried and leaves the legal conclusion out. The MIT text was already reproduced in full, so nothing about the obligation changes — only the accuracy of the sentence next to it. Upstream copyright lines were checked against the upstream LICENSE files rather than transcribed: both are `Copyright (c) 2025 opencode` and `Copyright (c) 2025 models.dev` verbatim. The generated headers name their payload. models.dev serves a rolling document with no version, so the generator stamps the sha256 of the data it extracted — not of api.json, which would move whenever any of the ~40 dropped providers changed and produce header-only diffs that say nothing about the file they sit in. Hashing after extraction makes the digest describe what ships and keeps the generator a pure function of its input: the same api.json produces the same bytes on any day. Verified by running it twice against the pinned payload. Closes the first exit criterion of #3270. Generated-by: Claude Code
…ifact notice The generator's inventory is the npm production dependency closure. That is a complete answer to "which npm packages ship" and a structurally blind one to "what else ships": vendored, adapted, and generated-from-upstream material is not a package, so no amount of checking the walk's output can reach it. The previous commit's LICENSE entries were guarded by nothing — an attribution header could be dropped in a routine edit and both notices would still regenerate clean. The obvious move is to declare that material in the generator. The first draft did, and it was wrong: it restated the repository, revision, copyright, and license text that LICENSE already holds, creating a second authority that could disagree with the first while every check passed. Passing a deliberately wrong repository and revision to that validator was accepted without complaint, and the notice printed them. LICENSE is the authority. ASF release policy puts the notice there, it ships with every artifact, and a human reviews it — and its THIRD-PARTY COMPONENTS section is already structured. So the generator parses it and reproduces the entries verbatim into both notices, and this module keeps only the one fact LICENSE cannot express: which file carries each entry and what text must still be in it. That inverts what the check is worth: - All four entries reach the notices, not the two a hand-written list happened to name. Geist is OFL-1.1 and was structurally undeclarable before, because the draft could only emit MIT. - License text is LICENSE's own, not rebuilt from a template around a hand-transcribed copyright line. - The check runs both ways. A LICENSE entry with no carrier decision fails, so #2907's Vercel AI SDK and Astryx entries cannot land unanchored; a carrier decision with no LICENSE entry fails too. - `trycua/cua` is now anchored. `palette.ts` calls itself a "faithful 1:1 port" — a stronger claim than the opencode adaptation — and nothing had been holding it. The notices' own prose no longer implies more than it delivers: it says the entries are LICENSE's section reproduced and checked against their carriers, not that everything vendored has been found. Discovering undeclared material is a scan whose allowlist is #2907's audit table, and it belongs there. Validation takes its inputs as arguments, so every failure path is exercised against fixtures rather than by editing tracked source. `non-npm-sources.mjs` and both test files are wired into the CI planner's CLI-package inputs, the Windows release path filter, and `check:release`, so changing the guard runs the guard. Generated-by: Claude Code
Astro-Han
force-pushed
the
docs/3270-third-party-attribution
branch
from
August 20, 2026 20:32
c34f687 to
8311320
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Per-artifact third-party notices are generated from the npm production dependency closure, which by construction cannot reach vendored, adapted, or generated-from-upstream material. The root
LICENSEcarried that material as prose, nothing checked it against the tree, and one defect copied all of it into a single package's notice.This makes
LICENSEthe authority. The generator parses itsTHIRD-PARTY COMPONENTSsection, reproduces every entry verbatim into both notices, and fails if an entry and its carrying file disagree — in either direction. All four entries now reach the notices instead of the two a hand-written list would name, any license works (Geist is OFL-1.1), andtrycua/cuagets anchored for the first time.fix(scripts)—@ai-sdk/provider-utilsships no license file, so the generator substituted the whole root LICENSE as its Apache-2.0 text, filing 140 lines about unrelated third parties under one package. Truncate at the section header, fail closed if it is missing. Prerequisite here, since commit 2's entries would otherwise ship inside that notice. Regression test comes from docs: close code origin audit #2907, which carries the same fix.docs— attribution headers intool-output.tsandedit-replace.ts, scoped to the adapted material (both files are mixed-origin, so no whole-file SPDX), plus the two LICENSE entries.build(scripts)— the LICENSE parser, the carrier map, and CI wiring.Refs #3270 — closes its first exit criterion. Refs #2907.
Review focus
model.nameandmodel.descriptionverbatim (1779 upstream-authored descriptions), and the selection and ordering are the generator's own. MIT was always reproduced in full, so the obligation never rested on that sentence — only its accuracy did.edit.tscredits gemini-cli'seditCorrector, which is Copyright 2025 Google LLC under Apache-2.0; its escape set shares the first eight branches, in order, with the replacer here. Whether that carries protected expression is a licensing call, not mine.Known limits
Proves LICENSE's declarations are still true in the tree; does not discover undeclared material. That scan's allowlist is #2907's audit table, so it belongs there.
Verification
Both notice targets regenerated and both check scripts verified — the desktop one passes locally while the CLI one drifts.
Every failure path falsified end to end against the real tree: removed a carrier's marker, added a LICENSE entry with no carrier, removed a LICENSE entry with its carrier left behind, renamed the section header. All four fail with the specific error; each reverted immediately after.
Upstream copyright lines checked against the upstream LICENSE files rather than transcribed. Generator determinism checked: two runs against the pinned
api.jsonare byte-identical, and the digest now hashes the extracted payload rather than api.json, so dropped providers no longer produce header-only diffs. The generated files differ frommainby +6 lines, −0 — header only, bodies byte-identical.format:check,lint, core/runtime typecheck, 12 script tests, 26 runtime tests — all clean. Not run: the full repository suite.AI use
Tool(s) and scope: Claude Code drafted the attribution text, LICENSE entries, parser, carrier map, and tests, ran every check above, and coordinated two adversarial reviews (Codex and a Claude subagent) whose findings drove the LICENSE-as-authority redesign. The upstream comparisons against opencode
fc80874and gemini-cli, the license determinations, and the decision to leave copyrightability unadjudicated were reviewed by the contributor of record.Checklist
Does this PR entail a change in behavior?
No runtime behavior changes; the two runtime files gain comments only. Both
THIRD_PARTY_NOTICES.txtartifacts do change content, which is the point, andcheck:release, the CI planner's CLI-package inputs, and the Windows release path filter gain entries.