Documentation
¶
Overview ¶
Package clagate — вклад стороннего автора не попадает в дерево без подтверждённого соглашения о вкладе.
Что здесь на кону. Продукт выходит под AGPL-3.0-or-later, и владелец сохраняет за собой возможность выдать его же на других условиях (двойное лицензирование). Возможность эта держится ровно на одном: у правообладателя есть право выдавать лицензии на ВЕСЬ код, включая чужие вклады. Первый же сторонний вклад без соглашения эту возможность закрывает — и закрывает НЕОБРАТИМО: вернуться можно только собрав подпись каждого стороннего автора поимённо, а найти их через год некому и не по чему.
Класс, который здесь закрывается, тем и живуч, что не производит симптома. Вклад принят, сборка зелёная, продукт работает; неверным становится утверждение о правах, а его никто не прогоняет. Заметить это можно только в тот день, когда лицензию решат сменить, — то есть тогда, когда чинить уже нечем.
ЧТО СЧИТАЕТСЯ ВКЛАДОМ — две формы, и обе под наблюдением: автор коммита и соавтор, названный трейлером `Co-authored-by`. Гейт, знающий только первую, вторую не отвергает и не принимает — он её НЕ ВИДИТ, а вклад существует. Коммиттер намеренно не считается вкладчиком: тот, кто перенёс или влил чужой коммит, содержимого не создавал.
ЧТО СЧИТАЕТСЯ ПОДТВЕРЖДЕНИЕМ — тоже две формы: подпись в завершающем блоке трейлеров (`Signed-off-by`, ключ регистронезависим) и явная запись в ведомости `cla-ledger.yaml` для тех, кто принял соглашение вне коммита. Подпись засчитывается ТОЛЬКО своей личности: «в сообщении есть Signed-off-by» и «вкладчик подтвердил соглашение» — разные утверждения, и подписаться за другого нельзя.
Гейт проверяет СВОЮ предпосылку. Он обоснован тем, что объявленная область существует в дереве и имеет историю: область, названная мимо дерева, даёт пустой обход — ноль коммитов, ноль находок и вид исправной работы. Поэтому объём осмотренного объявляется отдельным числом: «ноль находок» обязано быть отличимо от «ноль прочитанного».
Чего гейт НЕ делает и делать не может: он не устанавливает юридическую действительность соглашения и не проверяет, что подписавший имел право подписывать. Его предмет — что подтверждение ЕСТЬ и относится к тому, кто вложил.
Index ¶
Constants ¶
const HistoryFloorCommits = 500
HistoryFloorCommits — сколько коммитов области отличают ИСТОРИЮ ДОМЕНА от дерева, которое её не несёт.
Число здесь ПОРОГ, а не замер: на день заведения обход области давал 1132 коммита, и порог выбран заведомо ниже — чтобы не краснеть на росте истории и не требовать правки при каждом её пополнении. Он ОДИН на оба исхода `Classify`: второй экземпляр разошёлся бы с первым молча, и тогда «слепой обход» и «условие не создано» перестали бы быть двумя сторонами одной оси.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Entry ¶
type Entry struct {
// Email — личность вкладчика. Единица счёта здесь адрес, а не имя: имя у
// одного человека пишется по-разному (в истории домена один автор
// представлен двумя написаниями имени при одном адресе), поэтому предикат
// по имени мерил бы соглашение об именовании, а не личность.
Email string `yaml:"email"`
// Note — зачем эта запись здесь. Пустая причина записью не считается:
// «не спрашиваем» без основания и есть тот дефект, который гейт ищет.
Note string `yaml:"note"`
}
Entry — одна личность в ведомости.
type Finding ¶
type Finding struct {
Commit string
Identity string
Form string // чем внесён вклад: автор либо соавтор
Why string
}
Finding — один вклад без подтверждения соглашения.
type Ledger ¶
type Ledger struct {
// Scope — пути, чьи коммиты судятся, ОТНОСИТЕЛЬНО КАТАЛОГА САМОЙ ВЕДОМОСТИ.
//
// Относительно ведомости, а не корня дерева, — и различие несущее. Координата
// от корня (`services/iam`) верна ровно в одной посадке: ведомость ЕДЕТ с
// модулем, поэтому в самостоятельном клоне тот же файл объявлял бы область,
// которой там нет, обход был бы пуст, а ноль находок неотличим от чистого
// дерева (kacho#2239). Относительно себя ведомость верна в ОБЕИХ посадках без
// правки: `.` означает «модуль», где бы он ни лежал.
Scope []string `yaml:"scope"`
// Owners — личности правообладателя. Соглашение к ним не применяется:
// нельзя заключить его с самим собой.
Owners []Entry `yaml:"owners"`
// Signatories — сторонние авторы, принявшие соглашение вне коммита
// (подписанный документ, запись в задаче). Note называет, чем и когда.
Signatories []Entry `yaml:"signatories"`
// Waivers — машинные личности, чей вклад авторским произведением не
// является (обновление версий зависимостей). Note называет, почему.
Waivers []Entry `yaml:"waivers"`
}
Ledger — ведомость: кто свой, кто подтвердил соглашение вне коммита и чья личность освобождена от вопроса.
type Outcome ¶
type Outcome int
Outcome — исход боевого прогона. Их ТРИ, и третий не вычитается из вердикта и не зачитывается в успех.
const ( // OutcomeJudge — предпосылка построена: вердикт о соглашении выносится. OutcomeJudge Outcome = iota // OutcomeUnmetPremise — УСЛОВИЕ НЕ СОЗДАНО: дерево истории домена не несёт, // и вердикта о соглашении такой прогон не выносит НИ В ОДНУ сторону. OutcomeUnmetPremise // OutcomeBlindWalk — НАХОДКА: обход усечён либо область объявлена мимо // дерева. От предыдущего отличается ОДНИМ фактом — посадкой. OutcomeBlindWalk )
func Classify ¶
Classify разводит три исхода боевого прогона по ОДНОМУ дискриминатору — посадке судимого дерева.
───────────────────────────────────────────────────────────────────────────── ЗАЧЕМ ОТДЕЛЬНАЯ ФУНКЦИЯ, А НЕ ВЕТВЛЕНИЕ В ПРОБЕ
Ради инъекции. Проба идёт в том дереве, в котором её запустили: ни посадку, ни глубину истории она себе подать не может, поэтому ветвление внутри неё доказывается ровно одной стороной — той, что случилась. Здесь обе величины приходят аргументами, и каждая ось проверяется в обе стороны на месте.
───────────────────────────────────────────────────────────────────────────── ПОЧЕМУ ДИСКРИМИНАТОР — ПОСАДКА
Нехватка истории означает в двух деревьях РАЗНОЕ, и складывать их нельзя:
дерево платформы — историю домена оно несёт BY CONSTRUCTION, поэтому
короткий обход означает, что слеп САМ ОБХОД: усечён
диапазон либо область объявлена мимо дерева. НАХОДКА;
самостоятельная — истории может не быть вовсе: распакованный состав
посадка коммита, свежий `git init`, срез глубины у
арендатора. «Условие не создано».
Отсюда несущее свойство: **в дереве платформы пропуск НЕДОСТИЖИМ**. Не «его там не бывает», а недостижим — ветвь одна, и с `inPlatformTree=true` в неё не заходят ни при каком отчёте. Без этого гейт снимался бы поломкой ведомости: достаточно объявить область мимо дерева, чтобы он молча пропустился, — то есть маска, неотличимая от исправной работы.
ПОМЕТКА ИСТЕКАЕТ САМА. Настоящий клон арендатора историю несёт, и в нём гейт выносит вердикт как в монорепо; пропуск достаётся ровно тем деревьям, у которых спрашивать не о чем. Приедет история — пропуск прекратится, и снимать для этого ничего не нужно.
ЧЕГО ЗДЕСЬ НЕТ: суждения о самих находках. `Classify` отвечает, ЕСТЬ ЛИ у прогона предмет; что найдено в предмете — дело вызывающего.
func ClassifyStale ¶
ClassifyStale — вердикт о ЗАПИСЯХ ВЕДОМОСТИ, которым больше нечего покрывать.
ПОЧЕМУ ЭТО ОТДЕЛЬНЫЙ ВОПРОС, А НЕ ЧАСТЬ Classify. Та отвечает, есть ли у гейта предмет ВООБЩЕ (несёт ли дерево историю домена). Здесь предмет есть, и спрашивается другое: авторитетна ли эта история для суждения о том, чему запись ещё нужна.
ВЕДОМОСТЬ ЕДЕТ С МОДУЛЕМ, А ЕЁ ПРЕДМЕТЫ — НЕТ. Область объявлена как `.` именно затем, чтобы файл не правился при выносе. Но запись покрывает КОММИТ, а история артефакта — цепочка ПЕРЕПУБЛИКАЦИЙ: коммит дерева платформы в неё не попадает ни при каком построении. Значит запись, законная и не истекающая в стволе платформы (история не переписывается, коммит остаётся там навсегда), в самостоятельном клоне «нечего покрывать» ВСЕГДА — и краснела бы КАЖДОЙ поставкой, сообщая о посадке, а не о продукте.
САМОИСТЕЧЕНИЕ ПОСЛАБЛЕНИЯ ПРИ ЭТОМ НЕ СНЯТО. Оно остаётся строгим там, где история авторитетна, — в дереве платформы; ветвь пропуска туда недостижима by construction, ровно как у Classify. В клоне вердикт НЕ ВЫНОСИТСЯ, но причина и ЧИСЛО записей называются: «ноль находок» обязано быть отличимо от «ноль прочитанного», и молчаливый пропуск здесь дал бы ровно второе.
type Report ¶
type Report struct {
LedgerPath string
Scope []string
RevRange string
CommitsExamined int
ContributionsInspected int // пар «коммит × личность»
IdentitiesSeen int // различных адресов
ByOwners int
ConfirmedBySignOff int
ConfirmedByLedger int
Waived int
Findings []Finding
UnusedEntries []string
PremiseFailures []string
}
Report — итог обхода: и находки, и перепись осмотренного.
func Inspect ¶
Inspect судит историю объявленной области в диапазоне revRange.
ledgerRel — путь к ведомости ОТНОСИТЕЛЬНО repoRoot. Отсутствие ведомости — отказ, а не пустая ведомость: молчаливое умолчание здесь означало бы либо «своих нет, всё находки», либо «все свои», и ни то ни другое не выводится.
func (Report) Summary ¶
Summary — находки в ОГРАНИЧЕННОМ по объёму виде, годном для текста отказа.
Заведено инъекцией, а не предусмотрено: на синтетике находка была одна, и отказ читался; на живом дереве снятие своей личности из ведомости дало 928 находок разом, и вывод пробы схлопнулся В ПУСТОТУ — гейт краснел, не называя НИЧЕГО. Находка, не называющая координаты, посылает читателя искать не там, а потом её снимают как непонятную; поэтому объём отказа ограничен, а остаток назван числом, а не отброшен молча.