clagate

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 13, 2026 License: AGPL-3.0 Imports: 9 Imported by: 0

Documentation

Overview

Package clagate — вклад стороннего автора не попадает в дерево без подтверждённого соглашения о вкладе.

Что здесь на кону. Продукт выходит под AGPL-3.0-or-later, и владелец сохраняет за собой возможность выдать его же на других условиях (двойное лицензирование). Возможность эта держится ровно на одном: у правообладателя есть право выдавать лицензии на ВЕСЬ код, включая чужие вклады. Первый же сторонний вклад без соглашения эту возможность закрывает — и закрывает НЕОБРАТИМО: вернуться можно только собрав подпись каждого стороннего автора поимённо, а найти их через год некому и не по чему.

Класс, который здесь закрывается, тем и живуч, что не производит симптома. Вклад принят, сборка зелёная, продукт работает; неверным становится утверждение о правах, а его никто не прогоняет. Заметить это можно только в тот день, когда лицензию решат сменить, — то есть тогда, когда чинить уже нечем.

ЧТО СЧИТАЕТСЯ ВКЛАДОМ — две формы, и обе под наблюдением: автор коммита и соавтор, названный трейлером `Co-authored-by`. Гейт, знающий только первую, вторую не отвергает и не принимает — он её НЕ ВИДИТ, а вклад существует. Коммиттер намеренно не считается вкладчиком: тот, кто перенёс или влил чужой коммит, содержимого не создавал.

ЧТО СЧИТАЕТСЯ ПОДТВЕРЖДЕНИЕМ — тоже две формы: подпись в завершающем блоке трейлеров (`Signed-off-by`, ключ регистронезависим) и явная запись в ведомости `cla-ledger.yaml` для тех, кто принял соглашение вне коммита. Подпись засчитывается ТОЛЬКО своей личности: «в сообщении есть Signed-off-by» и «вкладчик подтвердил соглашение» — разные утверждения, и подписаться за другого нельзя.

Гейт проверяет СВОЮ предпосылку. Он обоснован тем, что объявленная область существует в дереве и имеет историю: область, названная мимо дерева, даёт пустой обход — ноль коммитов, ноль находок и вид исправной работы. Поэтому объём осмотренного объявляется отдельным числом: «ноль находок» обязано быть отличимо от «ноль прочитанного».

Чего гейт НЕ делает и делать не может: он не устанавливает юридическую действительность соглашения и не проверяет, что подписавший имел право подписывать. Его предмет — что подтверждение ЕСТЬ и относится к тому, кто вложил.

Index

Constants

View Source
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 — один вклад без подтверждения соглашения.

func (Finding) String

func (f Finding) String() string

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

func Classify(rep Report, inPlatformTree bool) (Outcome, string)

Classify разводит три исхода боевого прогона по ОДНОМУ дискриминатору — посадке судимого дерева.

───────────────────────────────────────────────────────────────────────────── ЗАЧЕМ ОТДЕЛЬНАЯ ФУНКЦИЯ, А НЕ ВЕТВЛЕНИЕ В ПРОБЕ

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

───────────────────────────────────────────────────────────────────────────── ПОЧЕМУ ДИСКРИМИНАТОР — ПОСАДКА

Нехватка истории означает в двух деревьях РАЗНОЕ, и складывать их нельзя:

дерево платформы     — историю домена оно несёт BY CONSTRUCTION, поэтому
                       короткий обход означает, что слеп САМ ОБХОД: усечён
                       диапазон либо область объявлена мимо дерева. НАХОДКА;
самостоятельная      — истории может не быть вовсе: распакованный состав
посадка                коммита, свежий `git init`, срез глубины у
                       арендатора. «Условие не создано».

Отсюда несущее свойство: **в дереве платформы пропуск НЕДОСТИЖИМ**. Не «его там не бывает», а недостижим — ветвь одна, и с `inPlatformTree=true` в неё не заходят ни при каком отчёте. Без этого гейт снимался бы поломкой ведомости: достаточно объявить область мимо дерева, чтобы он молча пропустился, — то есть маска, неотличимая от исправной работы.

ПОМЕТКА ИСТЕКАЕТ САМА. Настоящий клон арендатора историю несёт, и в нём гейт выносит вердикт как в монорепо; пропуск достаётся ровно тем деревьям, у которых спрашивать не о чем. Приедет история — пропуск прекратится, и снимать для этого ничего не нужно.

ЧЕГО ЗДЕСЬ НЕТ: суждения о самих находках. `Classify` отвечает, ЕСТЬ ЛИ у прогона предмет; что найдено в предмете — дело вызывающего.

func ClassifyStale

func ClassifyStale(rep Report, inPlatformTree bool) (Outcome, string)

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

func Inspect(repoRoot, ledgerRel, revRange string) (Report, error)

Inspect судит историю объявленной области в диапазоне revRange.

ledgerRel — путь к ведомости ОТНОСИТЕЛЬНО repoRoot. Отсутствие ведомости — отказ, а не пустая ведомость: молчаливое умолчание здесь означало бы либо «своих нет, всё находки», либо «все свои», и ни то ни другое не выводится.

func (Report) Summary

func (r Report) Summary(limit int) string

Summary — находки в ОГРАНИЧЕННОМ по объёму виде, годном для текста отказа.

Заведено инъекцией, а не предусмотрено: на синтетике находка была одна, и отказ читался; на живом дереве снятие своей личности из ведомости дало 928 находок разом, и вывод пробы схлопнулся В ПУСТОТУ — гейт краснел, не называя НИЧЕГО. Находка, не называющая координаты, посылает читателя искать не там, а потом её снимают как непонятную; поэтому объём отказа ограничен, а остаток назван числом, а не отброшен молча.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL