Skip to content

docs: attribute the adapted opencode sources and the models.dev snapshot - #3325

Draft
Astro-Han wants to merge 3 commits into
mainfrom
docs/3270-third-party-attribution
Draft

docs: attribute the adapted opencode sources and the models.dev snapshot#3325
Astro-Han wants to merge 3 commits into
mainfrom
docs/3270-third-party-attribution

Conversation

@Astro-Han

@Astro-Han Astro-Han commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Summary

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 — the adapted opencode sources and the generated models.dev snapshot. Both are missing for the same structural reason: neither is an npm package, and scripts/generate-third-party-notices.mjs walks npm production dependency trees only.

Three commits, each independently green:

1. fix(scripts) — keep Maka's own third-party section out of a package notice. @ai-sdk/provider-utils ships no license file, so the generator substituted the root LICENSE as its Apache-2.0 text — the whole file, so 140 lines describing trycua/cua and the bundled fonts were filed under one npm package in both notices. This is a prerequisite, not a drive-by: the defect scales with LICENSE, so commit 2's entries would otherwise ship inside the provider-utils notice. The fix is fail-closed — split returns the whole string when the delimiter is absent, which would silently reinstate the bug. The regression test comes from #2907, which carries the same fix; whichever lands second drops the duplicate.

2. docs — attribute the two sources. Both runtime files record source, revision, license, and copyright, scoped to the adapted material, with no whole-file SPDX identifier because both files are mixed-origin.

3. build(scripts) — render the LICENSE third-party section into every artifact notice. The generator now parses LICENSE's THIRD-PARTY COMPONENTS section and reproduces the entries verbatim into both notices; scripts/non-npm-sources.mjs keeps only the one fact LICENSE cannot express — which file carries each entry and what text must still be in it.

What changed after adversarial review

Two independent reviewers (Codex and a Claude subagent, isolated worktrees) returned findings I verified myself. Three changed the design:

The models.dev rights claim was wrong in both halves, and is gone. The earlier draft asserted the individual records were uncopyrightable facts and that what came from upstream was "selection and arrangement". But sync-model-metadata.mjs:266 copies model.name and model.description verbatim — the snapshot carries 1779 upstream-authored descriptions like 'Reasoning-first Gemini preview for agentic coding and complex problem solving' — while the selection and ordering are the generator's own (localeCompare, and a hand-maintained provider list). The entry now states which fields are carried and leaves the legal conclusion out. MIT was already reproduced in full, so the obligation never depended on that sentence; only its accuracy did.

The attribution chain stopped one hop early. opencode's edit.ts credits cline diff-apply and the gemini-cli editCorrector. I checked gemini-cli's editCorrector.ts: Copyright 2025 Google LLC, Apache-2.0, and its escape set shares its first eight branches in order with the replacer here (n, t, r, ', ", \, \, \n`); opencode added a ninth and changed the pattern. LICENSE now records the whole chain and explicitly does not adjudicate whether that shared sequence carries protected expression — that is a licensing call, not mine.

The declared-inventory design created a second authority. The first draft restated repository, revision, copyright, and license text that LICENSE already holds. Codex demonstrated the consequence: passing a deliberately wrong repository and revision to the validator was accepted, and the notice printed them. LICENSE is the authority — ASF policy puts the notice there, it ships with every artifact, a human reviews it, and the section is already structured. Parsing it instead of restating it inverts what the check is worth:

before after
entries reaching the notices 2 (hand-listed) 4 (all of LICENSE's)
licenses supported MIT only any — Geist is OFL-1.1 and was structurally undeclarable
license text rebuilt from a template around a transcribed copyright line LICENSE's own, verbatim
check direction declared ⊆ tree both ways
trycua/cua unanchored anchored — palette.ts calls itself a "faithful 1:1 port"

The both-ways check matters for what lands next: #2907's Vercel AI SDK and Astryx entries cannot land unanchored, because a LICENSE entry with no carrier decision fails generation.

Also fixed from review: the digest now hashes the extracted payload rather than api.json (hashing api.json moved it whenever any of the ~40 dropped providers changed, producing header-only diffs), and the wall-clock date is gone, so the generator is a pure function of its input — same api.json, same bytes, any day. 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.

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.

Refs #3270 — closes its first exit criterion. Refs #2907.

Review focus

  • The models.dev entry's wording is still the thing most worth a second reading. Overstating a fact database as copied code would be as wrong as the claim it replaced.
  • The LICENSE parser is fail-closed by design. A malformed section throws rather than returning a short list, because a silently short list drops a notice obligation — the exact failure this path exists to prevent.
  • Whichever of docs: close code origin audit #2907 / this PR lands second must regenerate both notices and drop the duplicated two-line fix.

Known limits

  • This proves what LICENSE declares is still true in the tree, and that nothing anchored is missing from LICENSE. It does not discover undeclared material. That scan's allowlist is docs: close code origin audit #2907's audit table, so it belongs there — a grep for adapted from|ported from|vendored|copied from currently hits 68 lines across 20 files.

Verification

Both notice targets regenerated and both check scripts verified — running only the desktop one passes locally while the CLI one drifts.

Both-ways validation falsified end to end against the real tree, each reverted immediately after:

injected defect result
removed the revision marker from tool-output.ts no longer contains its attribution marker "fc80874f…"
added a LICENSE entry with no carrier decision LICENSE declares "example/new" but scripts/non-npm-sources.mjs does not say which file carries it
removed a LICENSE entry, left its carrier decision declares carriers for entries LICENSE does not have: Geist and Geist Mono (bundled webfonts)
renamed the THIRD-PARTY COMPONENTS header LICENSE has no THIRD-PARTY COMPONENTS section header to truncate at

Generator determinism checked, not asserted: two runs against the pinned api.json produce byte-identical output. The two generated files differ from main by +6 lines, −0 — header only, bodies byte-identical — so the header is a pure addition carrying no data change.

npm run check:third-party-notices       → OK
npm run check:cli-third-party-notices   → OK
npm run format:check                    → 1548 files, no fixes
npm run lint                            → 2455 files, no fixes
core / runtime typecheck                → clean
scripts/non-npm-sources.test.mjs + generate-third-party-notices.test.mjs → 12 pass, 0 fail
tool-output.test.js + edit-replace.test.js → 26 pass, 0 fail

Not run: the full repository suite. Nothing here changes runtime behavior — the two runtime files gain comments only, and the generated modules gain six header comment lines.

AI use

  • No generative tool made a substantive contribution
  • Generative tooling made a substantive contribution

Tool(s) and scope: Claude Code drafted the attribution text, LICENSE entries, the LICENSE parser and carrier map, and the tests, ran every check reported above, and coordinated two adversarial reviews (Codex, Claude subagent) whose findings drove the redesign described above. The upstream comparison against opencode fc80874 and gemini-cli, the license determinations, and the decision to leave copyrightability unadjudicated were reviewed by the contributor of record.

Checklist

  • Tests cover the change and fail without it
  • Lint, format, typecheck and the affected suites pass locally

Does this PR entail a change in behavior?

  • Yes — described under Summary above
  • No

@Astro-Han
Astro-Han force-pushed the chore/models-dev-refresh-catalog-snapshot branch 3 times, most recently from 31a9326 to 8aa2935 Compare August 20, 2026 14:44
@Astro-Han
Astro-Han force-pushed the docs/3270-third-party-attribution branch 2 times, most recently from 3eab973 to e4bdb38 Compare August 20, 2026 17:43
@Astro-Han
Astro-Han force-pushed the chore/models-dev-refresh-catalog-snapshot branch from 7e64456 to 8dca808 Compare August 20, 2026 17:48
@Astro-Han
Astro-Han force-pushed the docs/3270-third-party-attribution branch from e4bdb38 to 78dbe2c Compare August 20, 2026 17:49
Base automatically changed from chore/models-dev-refresh-catalog-snapshot to main August 20, 2026 18:34
@Astro-Han
Astro-Han force-pushed the docs/3270-third-party-attribution branch from 78dbe2c to c34f687 Compare August 20, 2026 18:35
…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
Astro-Han force-pushed the docs/3270-third-party-attribution branch from c34f687 to 8311320 Compare August 20, 2026 20:32
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