Поставка софта Лицензии как инженерное ограничение: копилефт и разрешительные
0%

Лицензии как инженерное ограничение: копилефт и разрешительные

Лицензии как инженерное ограничение: копилефт и разрешительные

Инженер добавляет в проект библиотеку для конвертации документов. Она делает ровно то, что нужно, у неё две тысячи звёзд, тесты зелёные, CI прошёл, ревью занял четыре минуты. Через год компания продаёт коробочную версию продукта крупному заказчику, тот присылает опросник поставщика, в опроснике пункт «перечислите открытые компоненты и их лицензии». Кто-то впервые запускает сканер. Библиотека — под GPL-3.0. Она вкомпилирована в единственный бинарь, который вы отдаёте заказчику на флешке.

Дальше есть три выхода, и все три стоят денег. Открыть исходники продукта. Выкинуть библиотеку и переписать её функциональность — примерно два человеко-месяца, потому что за год к ней приросло сорок мест в коде. Купить коммерческое исключение у автора, если автор один и если он согласен. Ни один из выходов не стоил бы ничего, если бы вопрос задали в тот момент, когда строка dependencies попала в pull request. Вот почему лицензия — это инженерное ограничение, а не юридическая формальность. Она ведёт себя ровно как ограничение архитектуры: дёшево учесть на входе, дорого исправить потом, и чем позже обнаружено, тем шире радиус переделки. В этой главе мы разбираем лицензии так же, как разбирали бы требования к латентности: какое ограничение накладывает каждое семейство, в какой момент оно срабатывает, где проходит граница, как проверить соблюдение автоматически и во что обходится сама проверка. Про способы довезти продукт до пользователя целиком — карта трека и модели поставки; здесь один срез: какие двери каждая модель поставки закрывает или открывает по лицензионным причинам.

Сразу и однозначно: это не юридическая консультация. Всё ниже — инженерная модель, которая помогает задать правильный вопрос и подготовить к разговору с юристом факты. Раздел «Что спросить у юриста» существует именно потому, что часть вопросов инженерным способом не решается в принципе.

Лицензия — это условное разрешение, а не описание характера проекта

Начнём с первого принципа, который переворачивает интуицию большинства.

По умолчанию — нельзя. Авторское право возникает автоматически в момент, когда код написан. Без отдельного разрешения посторонний человек не имеет права ни копировать код, ни распространять его, ни делать производные произведения. Репозиторий на GitHub без файла LICENSE — это не «код в свободном доступе», это код, который вам разрешили посмотреть и больше ничего. GitHub прямо пишет это в своей документации: без лицензии действуют правила по умолчанию, то есть все права у автора (choosealicense.com/no-permission).

Лицензия — это разрешение, выданное под условиями. Структура любой лицензии одинакова:

  1. Что разрешено. Использовать, копировать, изменять, распространять, сублицензировать, использовать в патентных целях.
  2. При каких условиях. Сохранить текст лицензии; указать авторов; отметить изменённые файлы; открыть исходники; не накладывать дополнительных ограничений.
  3. Чего не даётся. Гарантий, ответственности, прав на товарный знак, иногда — патентных прав.

Отсюда практическое правило, которое стоит выучить дословно: вопрос «а можно ли использовать эту библиотеку» бессмысленный. Осмысленный вопрос — «что мне придётся сделать, если я её использую и потом вот так поставлю продукт». Одна и та же GPL-библиотека безопасна во внутреннем сервисе, требует открыть код в коробочной поставке и попадает в серую зону в контейнере, который вы кладёте в публичный реестр. И сразу стоит отделить три правовых механизма, которые в инженерных разговорах постоянно смешивают:

Авторское право Патент Товарный знак
Что защищает конкретный текст кода техническое решение независимо от реализации имя, логотип, оформление
Даёт ли лицензия на код да, это её основной предмет только если сказано явно: Apache-2.0 даёт, MIT молчит почти никогда, обычно прямо исключено
Инженерное следствие нельзя копировать без соблюдения условий переписать «своими словами» не спасает форк надо переименовать и перебрендировать
Пример взяли файл из чужого репозитория реализовали запатентованный кодек назвали форк тем же именем

Именно из третьего столбца растут все истории вида «Debian пересобирает браузер и меняет ему имя»: код открыт, а имя и логотип — нет.

Три с половиной момента, когда лицензия просыпается

Ограничение срабатывает не тогда, когда вы скачали зависимость, а в конкретных событиях. Их немного. Использование. Просто запустить программу. Открытые лицензии здесь почти никогда ничего не требуют: GPL-3.0 прямо говорит, что запуск программы не ограничивается. А вот проприетарные и source-available лицензии ограничивают именно использование — числом ядер, числом пользователей, назначением. Модификация. Изменить код у себя. Само по себе тоже почти всегда свободно. Обязательства почти всех открытых лицензий привязаны не к модификации, а к следующему шагу.

Передача (distribution, в терминах GPL-3.0 — conveying). Вот главный триггер. Вы даёте другому лицу копию — в любой форме: инсталлятор, образ контейнера, прошивка в устройстве, JS-бандл в браузер, артефакт в реестр, архив подрядчику. Здесь просыпается всё: обязанность приложить текст лицензии, предложить исходники, не накладывать дополнительных ограничений. И половина момента — доступ по сети. Классическая GPL написана в мире, где софт передавали копиями, и SaaS в неё не попадает: вы крутите изменённый GPL-код на своём сервере, никому копию не передаёте, значит и обязательств нет. Это осознанное свойство, за которым закрепилось название «лазейка ASP». AGPL-3.0 закрывает её отдельным пунктом 13: если вы изменили программу и даёте пользователям взаимодействовать с ней удалённо по сети, вы обязаны предложить им исходники.

Обратите внимание на левую нижнюю ветку: «крутится внутри — обязательств почти нет». Это правда и одновременно самая частая ловушка. Модель поставки меняется: внутренний сервис становится продуктом, продукт начинают ставить на площадку заказчика, SaaS обрастает on-premise-версией для банков. Ограничение, которого «не было», внезапно оказывается на критическом пути релиза. Поэтому реестр компонентов ведут всегда, а решение принимают по самой жёсткой из планируемых моделей поставки, а не по текущей.

Что копилефт запрещает делать — конкретным списком

«Копилефт» звучит как идеология. Инженерно это набор запретов, и полезно держать их в голове списком, а не лозунгом. Нельзя отдать бинарь и не отдать исходники. GPL-2.0 в пункте 3 и GPL-3.0 в пункте 6 описывают варианты: приложить исходники к поставке; приложить письменное предложение выдать их в течение трёх лет; для сетевой раздачи — держать исходники на сервере рядом с бинарём. Причём отдать надо не «примерно тот код», а Corresponding Source: ровно ту версию плюс скрипты сборки, конфигурацию, определения интерфейсов — всё, что нужно, чтобы получатель пересобрал ровно этот артефакт. Это прямое требование к вашей системе сборки: если вы не умеете по номеру версии восстановить точное дерево исходников со всеми патчами, вы не сможете выполнить обязательство физически. Здесь лицензия смыкается с воспроизводимостью сборки (цепочка поставки).

Нельзя накладывать дополнительные ограничения. Формулировка GPL — «no further restrictions». Это ломает привычные проприетарные конструкции: NDA на код, запрет реверс-инжиниринга в EULA, ограничение числа установок, запрет передавать копию дальше. Если ваш стандартный договор содержит такие пункты, а в поставке есть GPL-компонент, договор и лицензия конфликтуют — вопрос «как развести наш EULA и GPL в одной поставке» типовой и именно юридический. Нельзя запретить пользователю поставить свою сборку на купленное устройство. GPL-3.0 в пункте 6 требует для «потребительских продуктов» приложить Installation Information — ключи, инструкции, всё, что нужно, чтобы поставить изменённую версию на само устройство. Пункт появился как ответ на практику, когда исходники формально отдавали, а прошивка проверяла подпись и чужую сборку не запускала. Инженерное следствие жёсткое: безопасная загрузка с закрытым набором ключей и GPL-3.0 в прошивке потребительского устройства плохо совместимы. Для встраиваемых продуктов это одно из главных ограничений выбора компонентов (трек про встраиваемые системы — про сами устройства).

Нельзя тихо перелицензировать. Права получены под условиями; сменить условия на выпущенную версию нельзя — это относится и к вашему собственному проекту, если у него больше одного автора (подробнее в разделе про CLA). Нельзя подать патентный иск и сохранить лицензию. GPL-3.0 в пункте 11 и Apache-2.0 в пункте 3 устроены так, что патентная атака на пользователей продукта прекращает вашу собственную лицензию. Для компании с патентным портфелем это не абстракция, а вход в список вопросов к юристу.

И симметрично — что копилефт не запрещает, вопреки распространённому мифу:

  • Не запрещает продавать. GPL прямо разрешает брать деньги за копии и за поддержку: она ограничивает не цену, а условия передачи.
  • Не заражает всё, к чему прикоснулась. Файл в вашем репозитории не становится GPL оттого, что рядом лежит GPL-библиотека: условие — вхождение в одну работу и передача.
  • Не требует публиковать код всему миру. Обязательство существует перед теми, кому вы передали бинарь: если заказчик один, исходники нужно предложить одному заказчику. То, что он потом вправе выложить их публично, — отдельный факт, который стоит осознавать заранее.
  • Не действует на неизменённое использование внутри. Пока копия не ушла наружу, триггера нет.

Где проходит граница «одной работы»

Это центральный технический вопрос копилефта, и честный ответ — граница определена не полностью. Есть позиция FSF, изложенная в GPL FAQ, есть практика индустрии, есть немногочисленные судебные решения, и они не покрывают все случаи.

Граница копилефта: чем слабее связь, тем слабее требование

Разберём случаи, в которых чаще всего ошибаются. Статическая линковка. Вы получаете один исполняемый файл, внутри которого куски чужого кода. Здесь спор практически отсутствует: индустрия работает по модели «это одна работа». Языки с преимущественно статической сборкой — Go, Rust, C++ с -static — делают этот случай основным, а не экзотическим. Инженер на Go, который тянет GPL-библиотеку в бинарь для коробочной поставки, получает ровно ту историю из начала главы.

Динамическая линковка. Позиция FSF: комбинированная работа, копилефт распространяется. Позиция части юристов и части компаний: связывание через стабильный публичный интерфейс — не производное произведение. Судебной практики, которая закрыла бы вопрос, мало. Практический вывод: не стройте бизнес-риск на том, что динамическая линковка «спасает» от GPL; она спасает от LGPL, потому что LGPL это явно разрешает.

LGPL и подмена библиотеки. Библиотечный копилефт формулирует требование инженерно: пользователь должен иметь возможность заменить библиотеку своей сборкой и получить работающий продукт. Динамическая линковка это условие выполняет естественно. При статической линковке LGPL требует дать объектные файлы вашего продукта, чтобы пользователь мог перелинковать. Для мобильных приложений это отдельная боль: iOS-сборка целиком статическая и подписанная, возможность перелинковки конфликтует с моделью подписи (релизы в магазины, а правила самих магазинов — в главе про магазины приложений).

Отдельный процесс. Ваша программа вызывает утилиту через exec, обменивается данными через pipe или локальный сокет — FSF называет это скорее двумя программами. Но граница размыта: если обмен идёт «сложными внутренними структурами данных», FSF считает это одной работой. Ориентир: чем ближе взаимодействие к «командная строка и поток байтов», тем безопаснее; чем ближе к «передаём указатели на общие объекты», тем опаснее. Простое объединение (mere aggregation). Диск дистрибутива с тысячей независимых программ не превращает их в одну работу — так живут Debian и любой репозиторий пакетов. При этом каждый пакет по-прежнему едет со своей лицензией и своими исходниками: «объединение» снимает только вопрос перетекания копилефта на соседей.

Образы контейнеров. Слой с debian:bookworm-slim или alpine — это десятки бинарей, часть под GPL. Публикуя образ в реестр, вы передаёте копии этих бинарей. Хорошая новость: обычно это простое объединение и на ваш код ничего не перетекает; ваш слой лежит поверх системных пакетов, а не сливается с ними. Плохая: обязанность отдать исходники системных компонентов формально ваша, а не Debian. Практика индустрии — ссылаться на исходники дистрибутива и хранить точный список пакетов с версиями в SBOM; здесь как раз спасает то, что дистрибутивы держат архивы исходников доступными. Об устройстве образов и реестров — контейнеры и реестры, об упаковке в целом — упаковка и установка.

Фронтенд-бандл. Ключевой и почти всегда пропускаемый случай. Когда браузер скачивает ваш app.min.js, вы передаёте пользователю объектный код — минифицированный результат сборки. Если в бандле есть копилефт-компонент, обязательства срабатывают ровно так же, как при выдаче инсталлятора. Отсутствие «установки» ничего не меняет: копия ушла на чужую машину.

Живой случай портала: инстанс it-tools под GPL-3.0

Этот портал даёт удобный разбор на реальном материале, а не на выдуманном. Мы поднимаем для читателей инстанс it-tools — набор утилит вроде конвертеров и генераторов. Проект под GPL-3.0. Мы не просто запустили ванильную сборку: к ней подключена собственная тема оформления Digitable Focus, то есть код изменён. Инстанс доступен читателям по адресу tools.digitable.life. Отдельно на портале работает Digitable Chat под GPL-2.0-or-later.

Разложим это по нашей же схеме. Момент срабатывания. Пользователь открывает страницу — браузер получает собранный JS/CSS-бандл, то есть объектный код. Триггер сработал независимо от того, что «мы ничего не устанавливаем на компьютер пользователя» и что «это же просто сайт». Объём обязательства. Не «исходники it-tools вообще» и не «ссылка на апстрим», а Corresponding Source той версии, которая запущена, вместе с нашей темой и конфигурацией сборки. Ссылка на репозиторий CorentinTh/it-tools обязательство не закрывает: там нет нашей темы, а значит это не тот код, который получил пользователь.

Как оформлено. В подвале каждой страницы стоят ссылки на форки с явным указанием лицензии. В шаблоне layouts/partials/site-footer.html рядом с ними лежат комментарии, объясняющие, почему ссылка именно тут:

{{/* tools.digitable.life — сборка it-tools под GPL-3.0. Лицензия обязывает
     дать получателям доступ к исходному коду той версии, которую мы
     запускаем, поэтому ссылка на форк стоит на сайте, а не подразумевается. */}}
<a href="https://github.com/digitable-lol/it-tools" rel="noopener">Исходники IT Tools · GPL-3.0</a>

Комментарий в шаблоне — не украшение: через полгода придёт человек, увидит «лишние» ссылки в подвале и захочет убрать их ради чистоты дизайна, и комментарий ответит ему до того, как он спросит.

Что здесь ещё не закрыто — и это честная часть примера. Ссылка ведёт на форк, а форк в момент написания совпадает с апстримом: gh api repos/digitable-lol/it-tools/compare/CorentinTh:main...digitable-lol:main возвращает "status": "identical". То есть ссылка есть, а кода запущенной версии за ней нет. Обязательство считается выполненным не тогда, когда ссылка появилась в подвале, а тогда, когда за ссылкой лежит именно тот код. В задачнике портала это два отдельных пункта: залить свою сборку в форк и поставить ссылку, причём второй зависит от первого. Пример показателен тем, что типичная ошибка здесь не «мы не знали про GPL», а «мы сделали видимую половину работы».

Случай Digitable Chat отличается одной деталью — лицензия GPL-2.0-or-later. Суффикс «or later» означает, что получатель вправе выбрать условия любой более поздней версии GPL. Для нас это практично: код под GPL-2.0-or-later можно комбинировать с кодом под GPL-3.0, а вот с чистой GPL-2.0-only — нельзя. Разница между GPL-2.0-only и GPL-2.0-or-later в идентификаторе SPDX — не бюрократия, а важный факт о совместимости.

Что из этого следует для сборки. Три технических требования, невидимых, пока не начнёшь выполнять обязательство всерьёз: артефакт должен знать свою версию и коммит, иначе не ответить, какие именно исходники соответствуют тому, что в проде; форк должен обновляться при каждом деплое изменённой сборки, а не «когда вспомним»; проверка «ссылка есть на каждой странице» должна жить в тестах, а не в памяти — тот же класс проверок, что релизные гейты в безопасных релизах.

А что тут спросить у юриста. Достаточно ли ссылки в подвале как способа «предложить» исходники в смысле GPL-3.0 для веб-приложения, или требуется более явное уведомление; обязаны ли мы хранить исторические версии исходников для сборок, снятых с эксплуатации. Вопросы с конкретным ответом, но не инженерным.

AGPL: копилефт, который срабатывает без передачи копии

AGPL-3.0 — это GPL-3.0 плюс пункт 13. Его смысл в одной фразе: если вы изменили программу и дали пользователям работать с ней удалённо по сети, вы обязаны заметно предложить этим пользователям Corresponding Source изменённой версии.

Три инженерных вывода. Триггер — модификация, а не запуск. Ванильная AGPL-программа, поднятая как есть, пункт 13 не активирует. Но «ванильная» надо понимать буквально: патч, чтобы починить баг, — уже модификация. А правку конфигурации обычно модификацией не считают. Граница между «конфигурация» и «модификация» в реальных установках размыта, и она же — главный источник неопределённости.

Граница «программы» не совпадает с границей вашей системы. Ваш сервис ходит по HTTP в отдельно стоящий AGPL-сервис — это две программы, и код вашего сервиса не затрагивается. А вот AGPL-библиотека, вкомпилированная в ваш серверный бинарь, делает весь бинарь единой работой, и пункт 13 относится к нему целиком. Отсюда корпоративные запреты. Во многих компаниях AGPL просто в списке deny — не потому, что лицензия плоха, а потому, что цена ошибки высока, а граница нечёткая. Это управление риском, а не правовая позиция. Если вы строите SaaS, отношение к AGPL — одно из первых архитектурных решений; оно смыкается с выбором облачных уровней и с тем, где кончается ваш периметр. Полезная деталь: GPL-3.0 в своём пункте 13 специально разрешает комбинировать код с AGPL-3.0 — совместимость предусмотрена авторами обеих лицензий.

Разрешительные лицензии: одна обязанность, которую всё равно забывают

MIT, BSD-2-Clause, BSD-3-Clause, ISC устроены почти одинаково: делайте что хотите, включая закрытие исходников и продажу, но сохраните текст лицензии и указание авторства в копиях и в существенных частях. Всё. Гарантий нет, ответственности нет. Одна обязанность — и она нарушается постоянно, потому что незаметна. Мобильное приложение с сорока зависимостями под MIT обязано донести до пользователя сорок копирайт-нотисов. Отсюда экран «Правовая информация» в приложениях, файл THIRD-PARTY-NOTICES.txt в дистрибутиве, слой с лицензиями в образе контейнера. Это генерируется автоматически — вручную такое не поддерживают.

Apache-2.0 добавляет к этому три вещи, и они делают её другим инструментом, а не «MIT с длинным текстом»:

  • Явный патентный грант (пункт 3). Контрибьюторы дают вам патентную лицензию на своё вкладываемое. MIT об этом молчит, и молчание — источник неопределённости для компаний с юридической службой. Именно поэтому Apache-2.0 часто предпочитают в корпоративной среде.
  • Патентная ответная мера. Судитесь по патентам из-за этого софта — теряете патентную лицензию.
  • Требование отмечать изменения и сохранять NOTICE (пункт 4). Если в проекте есть файл NOTICE, его содержимое надо передавать дальше — конкретная задача сборки, а не благое пожелание.

А BSD-3-Clause отдельно запрещает использовать имя правообладателя для продвижения вашего продукта — тот самый переход в зону товарных знаков. А исторический BSD-4-Clause с «рекламным пунктом» (обязательная фраза в рекламных материалах) несовместим с GPL, и это самый известный пример того, как безобидное на вид требование ломает совместимость. Университет Беркли отозвал этот пункт в 1999 году, но лицензия ещё встречается в старом коде.

Правый верхний угол не означает «плохие лицензии» — он означает «несовместимые с одной конкретной моделью поставки, закрытым продуктом». Если ваша модель другая, копилефт может быть ровно тем, что нужно: он не даёт крупному игроку взять ваш код, закрыть и продавать. Именно так копилефт используют как инструмент конкуренции, и об этом — следующая глава. Отдельно про SSPL и BUSL в этом квадранте: у них мало обязательств по оформлению поставки, но они запрещают целые сценарии использования — принципиально другая ось, и на плоскость она ложится плохо, что само по себе хороший индикатор.

Совместимость: стрелки односторонние

Комбинируя код под разными лицензиями, вы получаете работу, к которой применимы все условия сразу. Если условия противоречат друг другу, комбинация невозможна вообще — не «рискованна», а невозможна.

Читается так: сплошная стрелка A --> B означает «код A можно включить в работу под лицензией B»; пунктир — запрещённое направление. Обратного пути нет: это односторонний граф, и в этом вся его суть. Результирующая лицензия комбинированной работы — самая ограничительная в цепочке, и добраться до неё можно, а вернуться — нет. Практические следствия, которые стоит помнить наизусть:

  • Apache-2.0 и GPL-2.0-only несовместимы. Позиция FSF: патентные условия Apache — дополнительное ограничение, которого GPL-2.0 не допускает. С GPL-3.0 совместимо, потому что третья версия такие условия предусмотрела. Отсюда, кстати, огромная практическая разница между ядром Linux (GPL-2.0-only) и большинством пользовательского софта.
  • Суффикс -or-later — это опцион. GPL-2.0-or-later совместим с GPL-3.0-миром, GPL-2.0-only — нет. В SPDX это разные идентификаторы, и сканеры их различают; человек, читающий заголовок файла, часто нет.
  • MPL-2.0 намеренно сделана мостом. В ней есть механизм «вторичной лицензии», позволяющий комбинировать с GPL, — поэтому MPL часто выбирают для библиотек, которые должны жить и в открытом, и в закрытом мире.
  • Проприетарная лицензия несовместима с любым сильным копилефтом в рамках одной работы. Не переговорная позиция, а арифметика условий.

Как посчитать результирующее обязательство: маленький алгоритм

Дерево зависимостей современного проекта — сотни, иногда тысячи узлов, и лицензия транзитивна: она приезжает к вам не только из прямой зависимости, но и из зависимости зависимости на четвёртом уровне. Считать это глазами нельзя. Считать это сканером — можно, и полезно понимать, что сканер делает. Модель простая: лицензии образуют решётку обязательств, где каждый уровень включает предыдущий. Результат для артефакта — максимум по всем достижимым узлам, но с поправкой: копилефт не перетекает через «слабые» рёбра — вызов отдельного процесса или сетевой вызов.

Псевдокод:

функция посчитать(граф, корень):
    итог ← НЕТ; отдать_исходники ← ∅
    обход в глубину от корня с флагом «связь сильная» = истина:
        если связь сильная и лицензия(узел) — копилефт:
            итог ← максимум(итог, уровень(лицензия(узел)))
        если узел физически попадает в поставку и это копилефт:
            отдать_исходники ← отдать_исходники ∪ {узел}
        для каждого ребра (узел → потомок):
            новый_флаг ← связь_сильная И тип_ребра ∈ {компиляция, линковка, вендоринг}
    вернуть итог, отдать_исходники

Реализация:

from enum import IntEnum
from dataclasses import dataclass, field

class Obligation(IntEnum):
    """Решётка обязательств: каждый уровень строго включает предыдущий."""
    NONE = 0           # CC0, Unlicense, 0BSD
    NOTICE = 1         # MIT, BSD, ISC, Apache-2.0 — сохранить текст и авторство
    FILE_COPYLEFT = 2  # MPL-2.0, EPL-2.0 — открыть изменённые файлы
    LIB_COPYLEFT = 3   # LGPL — открыть библиотеку и дать её подменить
    WORK_COPYLEFT = 4  # GPL — открыть всю работу при передаче
    NET_COPYLEFT = 5   # AGPL — то же плюс при доступе по сети
    RESTRICTED = 6     # BUSL, SSPL, чужие проприетарные — запреты на сценарии

O = Obligation
LEVELS = {
    "MIT": O.NOTICE, "ISC": O.NOTICE, "BSD-3-Clause": O.NOTICE, "Apache-2.0": O.NOTICE,
    "MPL-2.0": O.FILE_COPYLEFT, "EPL-2.0": O.FILE_COPYLEFT,
    "LGPL-3.0-or-later": O.LIB_COPYLEFT,
    "GPL-2.0-only": O.WORK_COPYLEFT, "GPL-3.0-or-later": O.WORK_COPYLEFT,
    "AGPL-3.0-or-later": O.NET_COPYLEFT,
    "BUSL-1.1": O.RESTRICTED, "SSPL-1.0": O.RESTRICTED,
    "Proprietary": O.NONE,  # ваш собственный закрытый код
}
# Рёбра, через которые копилефт перетекает на вашу работу.
STRONG_EDGES = {"compile", "static-link", "dynamic-link", "vendor"}

@dataclass
class Node:
    name: str
    license: str
    shipped: bool = True                       # едет ли компонент в поставку
    deps: list[tuple[str, str]] = field(default_factory=list)  # (имя, тип ребра)

def compute(graph: dict[str, Node], root: str):
    """Уровень обязательства на вашу работу + компоненты, для которых
    надо быть готовым отдать исходники."""
    effective, sources = Obligation.NONE, set()
    seen, stack = set(), [(root, True)]
    while stack:
        name, strong = stack.pop()
        if (name, strong) in seen:
            continue
        seen.add((name, strong))
        node = graph.get(name)
        if node is None:
            sources.add(f"{name} (нет в графе)")
            continue
        level = LEVELS.get(node.license)
        if level is None:
            # Неоценимая зависимость опаснее известной плохой: в ручную очередь.
            sources.add(f"{name} ({node.license}: нет в справочнике)")
        elif level >= Obligation.FILE_COPYLEFT:
            if strong:
                effective = max(effective, level)
            if node.shipped:
                sources.add(name)
        for dep, edge in node.deps:
            stack.append((dep, strong and edge in STRONG_EDGES))
    return effective, sorted(sources)

# Наш бинарь статически тянет MIT-библиотеку, внутри которой LGPL,
# и отдельно запускает GPL-утилиту как процесс.
graph = {
    "our-app": Node("our-app", "Proprietary",
                    deps=[("fastjson", "static-link"), ("imagetool", "exec")]),
    "fastjson": Node("fastjson", "MIT", deps=[("codecs", "static-link")]),
    "codecs": Node("codecs", "LGPL-3.0-or-later"),
    "imagetool": Node("imagetool", "GPL-3.0-or-later"),
}
level, sources = compute(graph, "our-app")
print(level.name)   # LIB_COPYLEFT — GPL через exec на нас не перетёк
print(sources)      # ['codecs', 'imagetool'] — исходники нужны для обоих

Сложность — O(V + E) по времени и O(V) по памяти; удвоение из-за флага силы связи константу не меняет. На дереве в десять тысяч узлов это доли секунды, то есть проверку спокойно ставят в каждый pull request.

Ограничение модели важнее самой модели. Результат — это очередь на ревью, а не вывод. Сканер не знает, статически вы линкуете или динамически, не отличит конфигурацию от модификации, не увидит, что «отдельный процесс» обменивается сложными структурами. Автоматика ловит очевидное и снимает 90 % шума; оставшиеся 10 % — там, где нужны человек и юрист.

Как это живёт в репозитории и в конвейере

Разница между «мы соблюдаем лицензии» и «мы можем это доказать» — примерно как между «у нас есть бэкапы» и «мы восстанавливались из них в прошлом квартале». В исходниках. Файл LICENSE в корне; NOTICE, если он требуется Apache-компонентами; заголовок SPDX-License-Identifier в файлах. Последнее — не бюрократия: машиночитаемая метка избавляет от угадывания по тексту. Спецификация REUSE описывает, как разметить репозиторий так, чтобы лицензия каждого файла определялась однозначно, включая картинки и конфиги, у которых нет места под комментарий.

# Проверка полноты разметки: у каждого ли файла определена лицензия
reuse lint

# Инвентаризация зависимостей — по одному инструменту на экосистему
cargo deny check licenses   # Rust
go-licenses report ./...    # Go
pip-licenses --format=csv   # Python
license-checker --production  # npm

# Сборка SBOM из готового артефакта, а не из манифеста: видит то, что реально внутри
syft packages docker:registry.example.com/app:4.2.1 -o cyclonedx-json > sbom.json

Разница между «из манифеста» и «из артефакта» существеннее, чем кажется: манифест описывает намерение, артефакт — факт. Внутрь образа попадают системные пакеты, скачанные бинарники, вкомпилированные статические библиотеки, ничего этого в package.json нет. SBOM из артефакта — тот же принцип, что и в цепочке поставки: доверяем тому, что измерили. В конвейере. Гейт устроен как политика со списками, а не как «сканер что-то сказал»:

# Фрагмент политики лицензий. Живёт в репозитории, ревьюится как код.
allow:  # можно без вопросов
  [MIT, ISC, BSD-2-Clause, BSD-3-Clause, Apache-2.0,
   MPL-2.0]                  # файловый копилефт: наш код не задет

review:  # можно, но с записанным решением
  [LGPL-2.1-or-later, LGPL-3.0-or-later, EPL-2.0,
   CC-BY-4.0]                # для ассетов: нужен экран атрибуции

deny:  # блокирует сборку
  [GPL-2.0-only, GPL-3.0-or-later, AGPL-3.0-or-later, SSPL-1.0, BUSL-1.1,
   CC-BY-NC-4.0,             # некоммерческое — несовместимо с продажей
   NOASSERTION]              # лицензия не определена: самый частый вердикт

exceptions:
  - package: "imagetool"
    license: "GPL-3.0-or-later"
    reason: "запускается как отдельный процесс, в поставку не входит"
    approved_by: "legal-2026-03-14"
    expires: "2027-03-14"    # исключения протухают, иначе список растёт вечно

Три свойства этого файла делают его рабочим, а не декоративным.

  1. deny жёстко ломает сборку. Предупреждение, которое никого не будит, равно отсутствию проверки. review существует отдельно от deny. Иначе инженеры начинают обходить гейт, а вы теряете видимость — та же динамика, что с любым слишком строгим контролем в конвейере.
  2. Исключения именованы и датированы. Без срока годности список исключений через два года становится главным источником риска.
  3. NOASSERTION в deny. Неопределённая лицензия — самая частая находка сканера и самая недооценённая: пакет без LICENSE по умолчанию не разрешён к использованию вообще.

Переход Разрешён --> Пересмотр — тот, который забывают закладывать. Проект может сменить лицензию в новой версии, и обновление зависимости на минорную версию иногда меняет лицензионный статус. Именно так BUSL приходил в проекты, годами живших на Apache-2.0. Поэтому сканирование в CI повторяется на каждой сборке, а не «один раз при добавлении».

Когда приходит запрос на исходники

Обязательство редко проверяют, но когда проверяют — быстро. Полезно один раз прогнать сценарий целиком и убедиться, что он выполним.

Ветка «else» — не выдумка, это самый частый способ провалиться. Компания искренне готова отдать исходники и не может: артефакт собран год назад с плавающими версиями зависимостей, которые с тех пор обновились, тег потерян, скрипт сборки жил на ноутбуке уволившегося. Поэтому воспроизводимость сборки и лицензионное соответствие — одна и та же инженерная задача, решаемая одними средствами: пиннинг версий, фиксация тулчейна, хранение SBOM вместе с артефактом, неизменяемые теги релизов. И отдельно про сроки: GPL-2.0 упоминает три года для письменного предложения исходников — значит, архивы релизов надо хранить дольше, чем живёт версия в проде, а политика удаления артефактов из реестра должна знать про этот срок.

Двойное лицензирование, CLA и почему нельзя передумать

Правообладатель не связан собственной лицензией: он может выдавать код на разных условиях разным людям. Отсюда классическая схема двойного лицензирования: открытая версия под сильным копилефтом плюс платная коммерческая лицензия для тех, кому копилефт мешает. Копилефт здесь работает не как идеология, а как продающий механизм — он делает бесплатный вариант непригодным ровно для тех, у кого есть деньги. По этой схеме десятилетиями живут Qt и целый класс инструментов; экономика такой модели — в главе про ценообразование и в материалах о собственном продукте. Схема работает, только если все права у вас одних. Как только появился внешний контрибьютор, его код принадлежит ему, и продать комбинацию под коммерческой лицензией вы уже не вправе. Отсюда два разных инструмента, которые постоянно путают:

DCO (Developer Certificate of Origin) CLA (Contributor License Agreement)
Что делает контрибьютор подтверждает Signed-off-by, что вправе отдать код подписывает соглашение с правообладателем
Передаются ли права нет, автор остаётся автором да: передача прав или широкая лицензия проекту
Позволяет ли двойное лицензирование нет да
Цена для проекта ноль, одна строка в коммите юридический процесс, отпугивает часть контрибьюторов
Типично для Linux, большинство сообществных проектов проекты за одной компанией

Механику подписи коммитов и работу с внешними участниками — в совместной работе в Git. И самое неинтуитивное: перелицензировать выпущенное нельзя. Версия 3.1, отданная под MIT, останется под MIT навсегда, у всех, кто её получил. Смена лицензии касается только будущих версий и требует согласия всех, чей код в проекте. Именно поэтому смены лицензии крупными проектами регулярно рождают форки: сообщество берёт последний коммит под старой лицензией и продолжает — так появились форки инструментов инфраструктуры и хранилищ данных после перехода их владельцев на source-available. Инженерное следствие для вас как потребителя: зафиксированная версия зависимости не может «стать» BUSL задним числом; вы просто окажетесь на ветке без обновлений и без исправлений безопасности, и это уже вопрос обновлений и поддержки старых версий.

Source-available: похоже на открытое, но им не является

Отдельное семейство, выросшее из конфликта «мы пишем, облака зарабатывают». Общая идея: исходники видны, но использование ограничено сценариями.

  • BUSL-1.1 (Business Source License). Использование ограничено, кроме разрешённого владельцем сценария (Additional Use Grant); в дату Change Date каждая версия автоматически переходит на открытую лицензию. Обычно запрещено именно «предлагать этот продукт как сервис». Механизм отложенного открытия ограничен сроком, прописанным в самой лицензии; в том же жанре работают «функциональные» лицензии с более коротким сроком и заранее названной будущей открытой лицензией.
  • SSPL. Разрешает предлагать сервис, но требует открыть весь стек его обслуживания — оркестрацию, мониторинг, инструменты управления. Требование заведомо неисполнимо для большинства, что и есть его цель.

Ключевой факт: OSI не признаёт эти лицензии открытыми, потому что определение Open Source запрещает дискриминацию по областям применения. Это не оценка, а классификация: слова «open source» в маркетинге такого продукта неточны. Инженерное следствие тоже специфическое. Обязательств по оформлению поставки почти нет — нет требования открыть свой код, нет обязанности отдавать исходники. Есть запрет на бизнес-сценарий, и проверяется он не сканером, а вопросом «а что именно мы продаём». Если ваша компания перепродаёт хостинг чужого движка, вопрос критический; если использует его внутри — обычно нет. Тем не менее держите такие лицензии в deny с обязательным ревью: сценарий использования меняется вместе с продуктом, а лицензия про сценарии.

Волна переходов на source-available пришлась на 2023–2025 годы и затронула популярные инфраструктурные инструменты и хранилища; часть из них позже частично вернулась к открытым лицензиям, часть осталась, почти каждый переход породил форк под управлением фонда. Проверяйте текущий статус конкретного проекта на момент чтения — за последние три года многие из них менялись дважды. Ставить в статью таблицу «кто под чем» бессмысленно: она устареет быстрее, чем вы дочитаете трек.

Не только код: ассеты, шрифты, контент и модели

Самый грязный участок реестра — обычно не библиотеки, а всё остальное: инженеры аккуратны с package.json и беспечны с папкой assets.

Шрифты. SIL OFL 1.1 разрешает свободное использование и встраивание, но запрещает продавать сам шрифт отдельно и требует не использовать «зарезервированное имя шрифта» в модификациях. Коммерческие шрифты лицензируются по числу просмотров или приложений — для мобильного продукта или игры это реальная статья расходов. Иконки и UI-киты. Типичный набор: код под MIT, а сами иконки под CC BY 4.0, то есть с обязательной атрибуцией — для игры это отдельный экран титров, для сайта строка в подвале.

Контент, музыка, звук. Creative Commons — семейство, а не лицензия. CC BY требует атрибуции; CC BY-SA добавляет копилефт-подобное требование к производным; CC BY-NC запрещает коммерческое использование и потому несовместим с продаваемым продуктом — это самая частая и самая дорогая ошибка в игровых проектах, где ассеты набирают из разных источников. Разбор игровой специфики целиком — в главе про дистрибуцию игр.

Код с форумов и из ответов. Пользовательский контент на Stack Overflow лицензирован под CC BY-SA (с мая 2018 года — версия 4.0). Копилефт-подобное условие «производные под той же лицензией» плохо сочетается с проприетарным продуктом, а требование атрибуции почти никогда не выполняется. Скопированные пятьдесят строк — реальный, а не теоретический риск.

Датасеты, веса моделей и код, сгенерированный ассистентом. Зона, где привычные категории не работают. Лицензии на веса часто содержат ограничения по сценариям и порогам использования, то есть ведут себя как source-available, а не как открытые, и эти ограничения обязаны доехать до вашего пользователя в вашем же соглашении. К длинным сгенерированным фрагментам разумно относиться как к коду неизвестного происхождения: прогонять через тот же сканер, а не считать «своим» по умолчанию.

Во что обходится соблюдение

Разговор о цене владения полезно вести в человеко-часах. Порядки величин по опыту небольших команд, на момент лета 2026 года:

Работа Разовая настройка Постоянные расходы
Реестр компонентов и файл политики 1–2 дня ревью новых зависимостей, минуты на pull request
Гейт в CI и генерация нотисов 1–3 дня близко к нулю, пока не появляются исключения
Экран атрибуции в приложении полдня автогенерация при сборке
Воспроизводимая source-сборка для копилефт-компонентов 3–5 дней хранение архивов, порядок десятков гигабайт
Хранение релизов под трёхлетний срок плата за объектное хранилище, обычно единицы USD в месяц
Разбор одного конфликтного компонента с юристом от нескольких часов работы юриста

Сравните с ценой промаха: переписывание функциональности вместо GPL-библиотеки — недели; сорванная сделка из-за опросника поставщика — вся сделка; вынужденное открытие кода — стратегия компании. Асимметрия такая, что гейт окупается первым же предотвращённым случаем — тот же тип расчёта, что в юнит-экономике: дешёвая постоянная работа против редкого дорогого события.

Что спросить у юриста — и что принести с собой

Инженер не решает правовые вопросы, но он единственный, кто может корректно их сформулировать. Плохой вопрос: «у нас тут GPL, это нормально?» Хороший содержит модель поставки, способ связывания и границу артефакта. Формулировки, которые работают:

  • «Мы отдаём заказчику один исполняемый файл, в который статически слинкована библиотека под LGPL-2.1. Достаточно ли для соблюдения условия о замене библиотеки отдать объектные файлы нашего продукта, или требуется динамическая линковка?»
  • «Наш SaaS использует библиотеку под AGPL-3.0, вкомпилированную в серверный бинарь. Мы поправили в ней два бага. Считается ли это модификацией, при которой срабатывает пункт 13, и кому именно мы обязаны предложить исходники — всем посетителям или только авторизованным пользователям?»
  • «Мы публикуем образ контейнера на базе Debian в публичный реестр. Считается ли это распространением бинарей системных пакетов с нашей стороны, и достаточно ли ссылки на архив исходников дистрибутива?»
  • «Наш стандартный договор запрещает заказчику передавать поставку третьим лицам. В поставке есть GPL-компонент. Как развести эти условия, чтобы не получить нарушения лицензии?»
  • «Мы хотим сменить лицензию проекта с MIT на BUSL. Что нужно от 14 внешних контрибьюторов и что делать с версиями, уже выпущенными под MIT?»

Что принести на встречу, чтобы она заняла час, а не месяц: SBOM артефакта, а не список из манифеста; схему сборки с указанием типов связывания; описание модели поставки и того, кто именно получает копию; текст вашего договора или EULA; список уже выданных исключений. Юрист без этих данных начнёт с их запроса, и это будет самый дорогой способ их собрать. Про сами договоры с заказчиком — материал о контрактах; юридические страницы этого портала — оферта, политика ПДн, возвраты — тоже живой пример того, как обязательства формулируются письменно.

Где инженерное решение принципиально невозможно. Является ли конкретная связь производным произведением; трактовка «модификации» для AGPL в вашей конфигурации; сочетание лицензии с вашим договором; юрисдикция и применимое право; риск патентов. Всё это не решается ни сканером, ни голосованием в чате.

Типичные ошибки

  • Спросили о лицензии на релизе, а не в pull request; проверили прямые зависимости, забыли транзитивные. Радиус переделки растёт нелинейно, а копилефт приезжает с четвёртого уровня дерева чаще, чем с первого.
  • Считают, что SaaS отменяет копилефт, и забывают про фронтенд-бандл. Отменяет для GPL, не отменяет для AGPL и совсем не отменяет, если завтра появится on-premise-версия; а браузер, получивший объектный код, — это состоявшаяся передача.
  • Ссылка на апстрим вместо своих исходников — или ссылка есть, а кода за ней нет. Обязательство про запущенную версию с вашими правками; половина работы выглядит как целая (см. случай it-tools выше).
  • NOASSERTION игнорируют, нотисы разрешительных лицензий не сохраняют, ассеты и шрифты держат вне реестра. Пакет без лицензии не «свободный по умолчанию», а «запрещённый по умолчанию»; самое лёгкое обязательство нарушают чаще всех; CC BY-NC в коммерческом продукте — классика игровых проектов.
  • Список исключений без сроков годности. За два года превращается в главный источник неучтённого риска.
  • Смена лицензии апстримом не отслеживается, прошлую версию не умеют пересобрать. Минорное обновление меняет правовой статус компонента, а обязательство выполнимо только при воспроизводимой сборке.
  • GPL-2.0-only и GPL-2.0-or-later считают одним и тем же. Разница определяет совместимость со всей третьей веткой.

Мини-итог

Лицензия — это условие на графе сборки и на способе поставки: она отвечает на вопрос, что вы обязаны сделать в момент, когда копия уходит другому лицу. Практический минимум для любого проекта, где софт выходит наружу:

  1. Реестр компонентов ведётся из артефакта, а не из манифеста. SBOM собирается на каждой сборке и хранится вместе с релизом.
  2. Политика лицензий лежит в репозитории и ревьюится как код. Три списка — allow, review, deny — плюс исключения со сроком годности.
  3. Гейт ломает сборку, а не пишет предупреждение. NOASSERTION — в deny.
  4. Решение принимается по самой жёсткой из планируемых моделей поставки, а не по текущей.
  5. Сборка воспроизводима — без этого обязательство отдать исходники физически невыполнимо.
  6. Нотисы и экраны атрибуции генерируются автоматически, а ассеты, шрифты и веса моделей учитываются наравне с кодом.
  7. Правовые вопросы задаются юристу в формулировках, содержащих модель поставки и способ связывания.

Проверьте себя одним вопросом: если завтра получатель вашей сборки попросит Corresponding Source версии, выпущенной девять месяцев назад, — соберёте? Если ответ «наверное», то ответ «нет».

Источники

Что дальше

Мы разобрали лицензию как ограничение: что она запрещает, где проходит граница, как проверять соблюдение автоматически. Но у открытого кода есть и обратная сторона — это полноценный способ поставки со своей экономикой и своей ценой владения, которую платит уже не потребитель компонента, а тот, кто его публикует.

Открытый код как способ поставки: что даёт и чего стоит

Нашли неточность? Выделите фрагмент текста — рядом появится жучок.

Нужен разбор именно вашей ситуации?

Статья описывает общий случай. Если у вас частный — можно разобрать его отдельно, платно. А если не хватает целого материала, предложите тему: её оплачивают вскладчину, и она выходит открытой для всех.

Доска запросов