Documentation
¶
Overview ¶
grant_removal_trace.go — миграция, СНИМАЮЩАЯ выдачи, обязана оставить след той формы, которую платформа для этого завела; и ни одна миграция не переносит выдачу с одной роли на другую.
Порт с монорепо (`internal/repohygiene/grantremovaltrace.go`, снят вынесением службы — `kacho#2597`; приёмка `seed-identity-names-its-own-service.md` §6 уже называла держателем `TestNoMigrationMovesGrantsBetweenRoles`, а он не существовал ни в одном файле — только в комментариях).
Предмет ¶
Строка `kaname.access_bindings` — это доступ, выданный арендатору. Миграция, удаляющая её, доступ ОТБИРАЕТ; и если рядом нет записи в журнал жизненного цикла выдачи, у которого есть читатель, то «отобрано» становится неотличимо от «никогда не выдавалось». Арендатор видит отсутствие права и не может узнать ни что оно было, ни когда исчезло, ни почему.
Замер на дне переезда (эта ревизия, `internal/migrations`) ¶
Корпус — 10 файлов. Ровно один называет таблицу выдач ОПЕРАТОРОМ удаления (`20260909202745_module_identities_leave_the_baseline.sql`), и он несёт след — вставку в `kaname.audit_outbox` — ДО удаления, в той же накатной половине. Храповик заведён нулём и это подтверждено переписью гейта, а не предположено: см. `TestMigrationRemovingGrantsLeavesATrace`.
Почему ХРАПОВИК, а не ведомость имён ¶
Применённую миграцию не правят (запрет #5) — значит найденную без следа придётся либо простить, либо держать ствол красным за прошлое. Ведомость имён растёт молча, и её никто не сверяет; число сверяется одним обходом и меняется только сознательно. Перечень имён при этом ПЕЧАТАЕТСЯ каждым прогоном — прощение остаётся видимым.
Храповик ДВУСТОРОННИЙ: находка приходит и на следующей миграции без следа, и на уменьшении числа прощённых. Односторонний потолок не истекает никогда и потому механизмом не является.
Почему снятие выдач У СУБЪЕКТА входит в класс ¶
Норма — про ОТОБРАННЫЙ ДОСТУП, а не про снятую роль: строки выдач исчезают одинаково, а служебная учётка — такой же первоклассный принципал, как человек.
Граница, названная честно ¶
Гейт судит НАКАТНУЮ половину: `DELETE` в откатной половине снимает СВОЮ ЖЕ вставку, откатываясь, и доступа не отбирает. Он не судит, ВЕРЕН ли текст оставленного следа и доедет ли он до читателя, — только что запись есть. И он ничего не говорит о выдачах, снятых прод-кодом: у того путь свой и свой журнал.
person_and_account_keys.go — каждый внешний ключ на `users` и `accounts` записан в ведомости вместе с действием и доводом (задача kaname#664, приёмка `own-account-and-identity-are-removed-in-order.md`, Р9, KN664-60/61).
Предмет ¶
Арендатор не мог уйти, потому что два ключа на `users` и `accounts` образовали цикл `RESTRICT`, а ещё три `RESTRICT` на `users` стояли на том же пути. Ни один из них не был ошибкой по отдельности — каждый заводился своей задачей без вопроса «как это снимается». Цикл вырос молча и молча вырастет снова, если новый ключ на эти таблицы не обязан называть своё действие.
Почему судится НАКАТАННАЯ схема, а не текст миграций ¶
Текстовая перепись не видит снятия таблицы вместе с её ключом и не различает ключ, объявленный в `CREATE TABLE`, и ключ `ADD CONSTRAINT` иначе, как разбором SQL. Каталог базы отвечает на сам вопрос: какие ключи на эти таблицы существуют после наката всей цепи. Поэтому держатель — интеграционная проба этого пакета, а судья ниже — чистая функция над тем, что она прочла.
Ведомость истекает сама ¶
Строка ведомости без ключа — находка, а не молчание: снятый ключ, о котором ведомость продолжает говорить, есть то же «два места об одном предмете».
sql_text.go — исполняемая часть миграции: Up-секция без комментариев и без содержимого строковых литералов.
Порт с монорепо (`internal/repohygiene/migrationsqltext.go`, снят вынесением службы — `kacho#2597`). Поведение переносом не менялось: функции перенесены дословно и экспортированы, чтобы их звала внешняя тестовая обёртка (`package migrations_test`), а не только пакет-владелец.
ЗДЕСЬ СТОЯЛО «в этом дереве сегодня один владелец — `grant_removal_trace.go`», и предостережение «заводя второй, проверь, не завёл ли кто-то копию». Оба пережили свой предмет, и второе не сработало НИ РАЗУ — именно потому, что первое лгало: читатель, которому сказали «владелец один», копию не искал.
Перемер 2026-09-14. ЕДИНИЦА СЧЁТА НАЗВАНА, потому что два образца об одном предмете дают разные числа: «файл называет имя» считает и прозу, и текст находки нового гейта (13), «файл ЗОВЁТ» считает вызовы (11). Ниже — второе.
git grep -lE '(MigrationUpSection|MigrationUpText)\(' -- '*.go' \
| grep -v internal/migrations/sql_text.go -> 11 файлов, из них 4 не-тестовых
git grep -ln 'strings.Index([a-z]*, *"-\{0,2\} *+goose' -- '*.go' -> 8, все _test.go
До сведения СВОИХ разрезов в дереве было одиннадцать, и они давали ПЯТЬ разных ответов на вопрос «что делать с файлом без маркера прямого хода»: отдать тело ЦЕЛИКОМ вместе с откатом · отдать пустую строку · отдать всё от начала · отказать · не спрашивать вовсе. Две копии жили в не-тестовом коде гейтов и сведены сюда (`kacho#2363`); восемь остались в пробах, где директива есть СОДЕРЖИМОЕ фикстуры, и это названная граница, а не чистота.
ДЕРЖИТСЯ ГЕЙТОМ, А НЕ ЭТОЙ ФРАЗОЙ: `internal/check` — `TestMigrationSectionSplitIsDeclaredOnce` требует, чтобы директива секции была выписана литералом только здесь, печатает объём осмотренного и падает на пустом обходе. Предостережение без держателя — то, что здесь уже стояло.
Index ¶
- Constants
- Variables
- func AuditGrantRoleReassignment(corpus []GrantMigrationSource) (moves []string, statements int)
- func BaselineVersion(fsys fs.FS) (int64, error)
- func ExpiryDeclarationsIn(bodies map[string]string) ([]ExpiryDeclaration, ExpiryCensus)
- func ExpiryDeclarationsInFS(fsys fs.FS) ([]ExpiryDeclaration, ExpiryCensus, error)
- func GrantRemovalFinding(got int, names []string) string
- func GrantTableMentions(corpus []GrantMigrationSource) int
- func JudgePersonAndAccountKeys(found []ForeignKeyOnPersonOrAccount, ledger []KeyLedgerRow) ([]string, error)
- func MigrationUpSection(body string) string
- func MigrationUpText(body string) string
- func OnDeleteWord(code string) (string, error)
- func SQLBlankComments(s string) string
- func SQLBlankStrings(s string) string
- type ExpiryCensus
- type ExpiryDeclaration
- type ForeignKeyOnPersonOrAccount
- type GrantMigrationSource
- type GrantRemovalCensus
- type KeyLedgerRow
Constants ¶
const ( // SectionMarkerStem — общее начало обеих директив секции. SectionMarkerStem = "-- +goose " // SectionDownMarker — начало ОБРАТНОГО хода миграции. SectionDownMarker = SectionMarkerStem + "Down" )
Директивы секции мигратора. ЛИТЕРАЛ ЖИВЁТ ЗДЕСЬ И БОЛЬШЕ НИГДЕ: выписанный второй раз, он превращается во второе место об одном предмете — и расходится молча, потому что на законном входе обе записи отвечают одинаково.
Стем объявлен отдельно, а маркер отката из него СОБРАН, чтобы у написания была одна точка правки: в этом дереве уже встречались три написания одного маркера (`-- +goose Down`, `+goose Down` без приставки и пара `Up`+`Down` с разрезом между ними), и различались они ровно тем, чего никто не решал.
const GrantRemovalRatchet = 0
GrantRemovalRatchet — сколько миграций корпуса СЕГОДНЯ снимают выдачи без следа. Число, а не перечень: см. шапку файла. Отклонение в любую сторону — находка.
const SessionTable = "human_sessions"
SessionTable — имя таблицы сессии человека без схемы. Одно объявление на пакет; адаптер называет ту же таблицу своим оператором.
Variables ¶
var ErrNoKeysWalked = fmt.Errorf("обход накатанной схемы не нашёл ни одного внешнего ключа на users и accounts — " +
"вердикт беспредметен: «ноль находок» неотличимо от «ноль прочитанного»")
ErrNoKeysWalked — обход каталога не нашёл ни одного ключа: вердикта нет.
var FS embed.FS
FS — embedded миграции (squashed baseline в одном файле). Используется cmd/migrator/main.go через runner.Config.FS.
var PersonAndAccountKeysLedger []byte
PersonAndAccountKeysLedger — ведомость ключей на `users` и `accounts`.
Functions ¶
func AuditGrantRoleReassignment ¶ added in v0.2.0
func AuditGrantRoleReassignment(corpus []GrantMigrationSource) (moves []string, statements int)
AuditGrantRoleReassignment — миграции, переставляющие выдачу с одной роли на другую, и число осмотренных операторов.
Второе число обязательно: ноль переносов при нуле прочитанных операторов означает «распознаватель ослеп», а не «переносов нет».
func BaselineVersion ¶ added in v0.5.0
BaselineVersion — версия СВОДА цепочки: наименьшая версия среди её миграций.
ВЫВОДИТСЯ ИЗ ЦЕПОЧКИ, А НЕ ВЫПИСЫВАЕТСЯ ЧИСЛОМ. Выписанное число не имеет производителя: сведение цепочки заново (а оно уже было — 171 миграция сведена в один файл) сдвинуло бы свод, и константа рядом осталась бы прежней молча.
Пустой обход — ОТКАЗ, а не ноль: «версий не нашлось» иначе неотличимо от «версий нет», и страж, который на этом строится, промолчал бы там, где ему полагается отказать.
func ExpiryDeclarationsIn ¶ added in v0.5.0
func ExpiryDeclarationsIn(bodies map[string]string) ([]ExpiryDeclaration, ExpiryCensus)
ExpiryDeclarationsIn — объявления срока у таблицы сессии по телам миграций (имя файла → текст). Тело режется на секцию наката и забеливается от комментариев ЗДЕСЬ, а не вызывающим: иначе два вызывающих разошлись бы в том, что считать исполняемым.
func ExpiryDeclarationsInFS ¶ added in v0.5.0
func ExpiryDeclarationsInFS(fsys fs.FS) ([]ExpiryDeclaration, ExpiryCensus, error)
ExpiryDeclarationsInFS — то же по встроенному каталогу миграций.
func GrantRemovalFinding ¶ added in v0.2.0
GrantRemovalFinding — текст находки. Отдельной функцией, чтобы обе стороны храповика (перебор и недобор) говорили ОДНИМ голосом и назывались вместе с перечнем.
func GrantTableMentions ¶ added in v0.2.0
func GrantTableMentions(corpus []GrantMigrationSource) int
GrantTableMentions — сколько операторов корпуса называют таблицу выдач ЛЮБЫМ глаголом. Общая предпосылка обоих гейтов файла: ноль означает, что корпус не прочитан либо таблица выдач из него исчезла.
func JudgePersonAndAccountKeys ¶ added in v0.5.0
func JudgePersonAndAccountKeys(found []ForeignKeyOnPersonOrAccount, ledger []KeyLedgerRow) ([]string, error)
JudgePersonAndAccountKeys сверяет ключи каталога с ведомостью. Находки — по одной строке на нарушение, отсортированы; пустой обход — ошибка.
func MigrationUpSection ¶ added in v0.2.0
MigrationUpSection — исполняемая часть миграции: Up-секция без комментариев.
Порядок двух шагов обязателен и обратим быть не может: маркер отката САМ является комментарием, поэтому секция отрезается по СЫРОМУ тексту, и только потом забеливаются комментарии. Забелив сперва, разбор потерял бы границу секций и прочитал бы откат как часть наката.
func MigrationUpText ¶ added in v0.5.0
MigrationUpText — ПРЯМОЙ ход миграции сырым текстом, без обработки комментариев.
Отделён от MigrationUpSection потому, что у разреза и у снятия комментариев РАЗНЫЕ вызывающие: часть гейтов дерева снимает комментарии своим разбором и нуждается только в разрезе. Пока отдельного входа не было, каждый такой гейт заводил СВОЙ разрез — и они разошлись в том, что делать с файлом без маркера прямого хода: один отдавал тело целиком ВМЕСТЕ С ОТКАТОМ, другой пустую строку, третий отказывал.
Здесь правило одно и оно названо: маркера отката нет — тело прямого хода есть весь текст; есть — прямой ход кончается на нём. Маркер ПРЯМОГО хода в разрезе не участвует вовсе: всё, что стоит до него, — шапка файла, то есть комментарий, и исполняемого в ней нет by construction.
func OnDeleteWord ¶ added in v0.5.0
OnDeleteWord — слово действия по коду каталога (`pg_constraint.confdeltype`). Неизвестный код — отказ, а не пустое слово: пустое совпало бы с пустым полем ведомости.
func SQLBlankComments ¶ added in v0.2.0
SQLBlankComments заменяет содержимое SQL-комментариев пробелами, сохраняя длину строки и позиции переводов строк.
Приоритет состояний: строка > комментарий > код. Внутри строкового литерала `--` комментария не начинает; внутри комментария кавычка строки не открывает. Блочные комментарии Postgres ВЛОЖЕННЫЕ — глубина считается, а не ищется первый `*/`.
func SQLBlankStrings ¶ added in v0.2.0
SQLBlankStrings заменяет СОДЕРЖИМОЕ строковых литералов пробелами, сохраняя длину и позиции переводов строк.
Зачем отдельно от SQLBlankComments. Тот забеливает комментарии и при этом НАМЕРЕННО пропускает литералы целиком — иначе `--` внутри строки открыл бы комментарий. Но литерал не исполняется как SQL: прозаическое сообщение об ошибке, содержащее слова конструкции, для инвентаря по образцу неотличимо от самой конструкции.
Кавычки-границы ОСТАЮТСЯ на месте: забеливается содержимое, поэтому `WHERE x = 'DROP COLUMN'` остаётся синтаксически тем же и по-прежнему не склеивает соседние лексемы.
Types ¶
type ExpiryCensus ¶ added in v0.5.0
type ExpiryCensus struct {
Files int // файлов миграций прочитано
TableMentions int // объявлений/изменений таблицы сессии найдено
ColumnsSeen int // столбцов таблицы сессии осмотрено
MomentColumns int // из них моментов, сроком не являющихся
ExpiryDeclared int // объявлений срока
}
ExpiryCensus — перепись обхода: печатается всегда, чтобы «ноль находок» было отличимо от «ноль прочитанного».
func (ExpiryCensus) String ¶ added in v0.5.0
func (c ExpiryCensus) String() string
type ExpiryDeclaration ¶ added in v0.5.0
type ExpiryDeclaration struct {
File string
Column string
Form string // "create-table" | "add-column"
Why string // чем распознано
}
ExpiryDeclaration — найденное объявление срока.
type ForeignKeyOnPersonOrAccount ¶ added in v0.5.0
type ForeignKeyOnPersonOrAccount struct {
Name string `json:"name"`
Table string `json:"table"`
References string `json:"references"`
// OnDelete — действие словом каталога: CASCADE, RESTRICT, SET NULL,
// NO ACTION, SET DEFAULT.
OnDelete string `json:"on_delete"`
}
ForeignKeyOnPersonOrAccount — внешний ключ, прочитанный из каталога накатанной схемы.
type GrantMigrationSource ¶ added in v0.2.0
GrantMigrationSource — один файл корпуса: имя для находки и текст для разбора.
func ReadMigrationCorpus ¶ added in v0.2.0
func ReadMigrationCorpus(paths []string, read func(string) ([]byte, error)) ([]GrantMigrationSource, error)
ReadMigrationCorpus — корпус миграций, состав берётся у вызывающего (индекс git либо `embed.FS`), а не с диска напрямую.
type GrantRemovalCensus ¶ added in v0.2.0
type GrantRemovalCensus struct {
FilesRead int
WithDelete int // удаление выдач где угодно в файле
InUpHalf int // из них — в накатной половине
}
GrantRemovalCensus — объём осмотренного. Три величины, а не одна: «ноль находок» обязано быть отличимо от «ноль прочитанного», а «ноль в накатной половине» — от «распознаватель перестал узнавать удаление вовсе».
func AuditGrantRemovalTrace ¶ added in v0.2.0
func AuditGrantRemovalTrace(corpus []GrantMigrationSource) (silent []string, c GrantRemovalCensus)
AuditGrantRemovalTrace — ядро гейта. Возвращает имена миграций, снимающих выдачи без следа, и перепись.
type KeyLedgerRow ¶ added in v0.5.0
type KeyLedgerRow struct {
ForeignKeyOnPersonOrAccount
Reason string `json:"reason"`
}
KeyLedgerRow — строка ведомости: ключ, его действие и довод.
func ParseKeyLedger ¶ added in v0.5.0
func ParseKeyLedger(raw []byte) ([]KeyLedgerRow, error)
ParseKeyLedger разбирает ведомость. Неизвестное поле — отказ: строка с опечаткой в имени поля иначе читалась бы как строка без довода.