Documentation
¶
Overview ¶
Package modelrender — блоки типов модели доступов ПОРОЖДАЮТСЯ из манифеста домена, а порождённое сверяется с каноном ПОБАЙТОВО (задача #1089, приёмка services/iam/docs/engineering/acceptance/model-generated-from-manifest.md).
Почему побайтово, а не «по смыслу» ¶
Сверка по смыслу требует разбора обеих сторон и потому измеряет СОГЛАСИЕ ДВУХ РАЗБОРЩИКОВ, а не согласие текстов. Разборщик, не знающий формы, на ней молчит, и расхождение уезжает в слепую зону. У байтов слепой зоны нет.
Модель есть контракт: лишняя строка в ней — лишнее право, недостающая — отказ там, где доступ выдан. Поэтому расхождение НАЗЫВАЕТ СТОРОНУ (§2 п. 3): эти два исхода чинятся разными правками и означают разное для арендатора.
Единица блока — тело, а не тело с баннером следующего домена ¶
Блок — от строки `type X` до ПЕРВОЙ ПУСТОЙ СТРОКИ. Комментарии канона стоят МЕЖДУ блоками (баннер домена принадлежит файлу, а не блоку), и единица «от `type X` до следующей строки `type`» приписала бы блоку чужой баннер.
Замер, разводящий две единицы на этом дереве (ревизия HEAD линии). ВЕЛИЧИНА НАЗЫВАЕТ СВОЮ ЕДИНИЦУ, и обе строки даны в обеих единицах: канон несёт кириллическую прозу, поэтому байт и символ здесь расходятся почти на четверть, и число без единицы рядом с числом в другой единице выглядит сопоставимым, не будучи им.
единица A — тело блока 32 блока · 90 701 Б · 66 891 симв · 739 внутриблочных комментариев единица B — тело плюс баннер 32 блока · 113 014 Б · 86 990 симв (весь файл)
Последний сдвиг величин — ПЕРЕЕЗД КОНТРАКТОВ (решение владельца 2026-09-13, kacho#2616): канон уехал в этот репозиторий, и абзац его шапки, утверждавший, что канон и вшитая копия лежат в разных деревьях, стал ложным и переписан. Тронут ТОЛЬКО баннер файла, ни один блок не изменился — поэтому единица A осталась прежней (90 701 Б / 66 891 симв), а единица B выросла на шестьсот девяносто байт. Это и есть довод в пользу двух единиц: сдвиг прозы шапки виден только в B. Величина прибавки написана словами намеренно: цифра рядом с обозначением единицы читается этим же гейтом как ЗАМЕР канона, и он справедливо требует, чтобы разбор такую величину давал.
Прежний сдвиг — переход написания якоря кластера (`cluster_kacho_root` → `cluster_root`, задача #2113): якорь назван в каноне ПЯТЬ раз, каждое называние короче на шесть знаков, и из пяти в телах блоков стоят ДВА — отсюда убыль в двенадцать байт у единицы A и в тридцать у единицы B. До него сдвиг был у переименования каталогов службы, до него — у имени схемы Postgres. Величины пересняты разбором, а не вычтены на бумаге: канон порождается из манифестов, и арифметика в уме дала бы совпадение, которое нечем проверить.
Канон УШЁЛ ВПЕРЁД от ревизии приёмки, и это сказано здесь, а не подразумевается ¶
Прежняя редакция шапки утверждала побайтовое равенство канона ревизии приёмки 5dcefe67. Равенства больше НЕТ: #1820 снял на кластере отношение `console` — читателя у него не было ни одного, и выдать его было нечем — и внёс на его место разбор снятия. Одним этим коммитом сдвинулись все пять величин выше; они пересняты разбором, а утверждение о равенстве снято вместе с предметом, потому что гейт величин судит ЧИСЛА и на пережившей себя прозе рядом с ними молчит by construction.
Единица счёта осталась ТОЙ ЖЕ, что в §0.7 приёмки, — строка-комментарий с отступом внутри тела `type`. На ревизии приёмки этот же счёт давал 720, и разборщик здесь с разборщиком приёмки тогда СОШЛИСЬ, а не «оба смолчали». Сходится ЕДИНИЦА, а не число: число есть свойство ревизии канона, и §0.7 остаётся верной записью о своей — переписывать её задним числом значило бы сделать ложным замер, который был верен.
Величины ВОСПРОИЗВОДЯТСЯ разбором, а не переписываются: гейт `TestCanonHeaderNamesItsUnitAndReproducesTheMeasurement` пересчитывает их из канона и падает на просроченной либо посчитанной в другой единице. В строке единицы A прежде стояло тело блоков В СИМВОЛАХ под обозначением байтов, рядом со строкой единицы B, где байты настоящие; из этой таблицы задача #1847 взяла своё число и построила на нём посылку (задача #1857).
Ошибочная пара здесь НЕ воспроизводится дословно намеренно: гейт судит всякую величину шапки, и цитата неверного числа краснила бы его на собственном объяснении.
Index ¶
- Constants
- Variables
- func CanonicalVerbOrder() map[string]int
- func OwnedTypes(resources []catalog.ResourceRow, dsl []byte, module string) []string
- func Render(r manifest.Resource) ([]byte, error)
- func Sweep(resources []catalog.ResourceRow, root string, waivers []Waiver) (Census, []Finding, int)
- func TypesOutsideModules(resources []catalog.ResourceRow, dsl []byte) []string
- type Block
- type Census
- type Finding
- type Side
- type Waiver
Constants ¶
const ( // SweepOK — сверено всё объявленное. SweepOK = 0 // SweepFinding — находка: любой повод перечня §2 п. 7 приёмки // `model-generated-from-manifest.md`. // // Перечень здесь НЕ воспроизводится и не сокращается до пары примеров. // Прежняя редакция называла два повода из перечисленных нормой поимённо и // пережила решение, которым перечень был сведён: второе место об одном // предмете расходится с первым молча, и расходится в сторону, где читатель // достраивает недостающее сам (задача #1856). SweepFinding = 1 // SweepVoid — сверять нечего ни для одного модуля. // // Отдельное значение, а не SweepOK: оболочка читает КОД, и «нечего» не вправе // выглядеть успехом. SweepVoid = 2 // SweepNotRun — условие сверки НЕ СОЗДАНО: канон в это дерево не поставляется. // // ───────────────────────────────────────────────────────────────────────── // ПОЧЕМУ ОТДЕЛЬНО ОТ SweepFinding // // Канон живёт в каталоге контрактов монорепо и в поставку модуля НЕ ВХОДИТ // by construction. В самостоятельном клоне сверять его не с чем — и это // «проверка НЕ ИСПОЛНЯЛАСЬ», а не находка о продукте. Прежде обход отдавал // здесь единицу, и всякий, кто склонировал модуль и позвал исполнителя // напрямую, получал КРАСНОЕ О ПРОДУКТЕ там, где красного нет: в монорепо та // же цель зелена (kacho#2241). // // «Не выполнилось» не вычитается из вердикта, не зачитывается в успех и не // выдаётся за находку (§«Чтение вердикта»). // // ───────────────────────────────────────────────────────────────────────── // ЧЕМ ЭТО ОТЛИЧАЕТСЯ ОТ НАСТОЯЩЕЙ ПРОПАЖИ КАНОНА // // Различает ОДИН факт: есть ли в судимом дереве каталог контрактов вообще. // Нет каталога — контракты не поставлены, условие не создано. Каталог есть, // а канона в нём нет — это находка: его удалили либо он переехал, и молчать // об этом нельзя. SweepNotRun = 3 )
sweep.go — обход ЗАКРЫТОГО НАБОРА модулей и сверка порождённого с каноном (Н-02, Н-05, Н-06 приёмки; сценарии B-06, B-08; инъекции C-08, C-09).
Перечень модулей задаёт НАБОР, а принадлежность блока — ТАБЛИЦА. Имя не задаёт ничего
Обратный порядок («сверяем модули, объявленные манифестами») при нуле манифестов вырождается в тождественную истину, а при частичном обходе — в зелёный вердикт над непрочитанным. Состояние «манифестов меньше, чем модулей» есть ШТАТНОЕ состояние миграции, а не переходный миг.
Счёт по префиксу имени типа ошибается в ОБЕ стороны сразу и это замер, а не вкус: модуль `iam` теряется целиком (`account` и `project` префикса не несут), а модуль балансировки канон зовёт `nlb_*`, тогда как набор — `loadbalancer`, и `module: nlb` загрузчик ОТВЕРГАЕТ. Под предикатом по префиксу сверка этого модуля не совпала бы НИ РАЗУ и отняла бы девять живых прав, выглядя рабочей.
Исходов ТРИ, и «частично» не является четвёртым
0 сверено всё объявленное — у каждого модуля набора манифест, все сверки прошли 1 находка — любой повод перечня §2 п. 7 приёмки (перечень там ОДИН) 2 VOID — сверять нечего НИ ДЛЯ ОДНОГО модуля 3 проверка НЕ ИСПОЛНЯЛАСЬ — канона в этом дереве нет by construction
VOID в успех НЕ ЗАСЧИТЫВАЕТСЯ и из вердикта НЕ ВЫЧИТАЕТСЯ: «зелёный под послаблением» неотличим от настоящего зелёного ПО КОДУ ВОЗВРАТА, а VOID отличим машинно.
Ведомость разрешает ПОЗАПИСНО и с номером, никогда огульно
Модуль, чей манифест ещё не приехал, стоит записью с номером задачи. Огульного «идёт миграция — проходим» не существует: у него нет предиката снятия, и оно не истекло бы НИКОГДА. Запись, которой больше нечего прощать (манифест приехал), — НАХОДКА, а не тишина.
Variables ¶
var ( // ErrObjectTypeEmpty — рендерить нечего: тип объекта не назван. ErrObjectTypeEmpty = errors.New("modelrender: resource objectType is empty") // ErrParentEmpty — указателей нет ни одного; указатель — первая строка блока, // и каскад супер-доступа выводится от его имени. ErrParentEmpty = errors.New("modelrender: resource has no parents") )
render.go — порождение блока типа из ресурса манифеста (Н-01 приёмки).
Что порождается, а что объявляется человеком
Раздел `resources` неоднороден (см. шапку manifest/resources.go), и рендер наследует это различие дословно:
порождается указатели · super_admin · ярусы · v_<глагол>
объявляется notes[] (проза с якорем) · relations[] (текст определения —
ДОСЛОВНО) вместе с их местом · cascade[] · источники яруса и
действия
Текст авторского отношения здесь НЕ разбирается: его грамматика принадлежит модели прав, и второй её разборщик разошёлся бы с первым МОЛЧА — на той самой форме, которой не знает.
Ярус выводится от ПРЕДЫДУЩЕГО яруса, а не от постоянной
Замер по канону: у `vpc_gateway` цепочка `admin → super_admin`, `editor → admin`, `viewer → editor`; у `vpc_address_pool` ярусов ДВА, и цепочка та же со снятым звеном — `admin → super_admin`, `viewer → admin`. Постоянная «viewer от editor» породила бы у второго ссылку на ярус, которого у него нет.
Цепочкой дело не исчерпывается: ярус вправе назвать СВОИ источники ключом `from`, и тогда они заменяют звено цепочки целиком. Замер: `account` несёт `or owner or super_admin`, `iam_user` — `or subject or editor`.
Каскад супер-доступа тоже приходит ИЗ МАНИФЕСТА
Написаний каскада в каноне четыре сверх умолчания, и восемь модульных блоков несут не умолчание. Форма структурная (`cascade[]` — пары `<relation> from <parent>`), а не текстовая: разбор строки завёл бы второй разборщик грамматики модели прав.
Субъекты сужают ЯРУСЫ и не трогают глаголы — это замер, а не симметрия
У `vpc_address_pool` ярусы несут `[user, service_account]`, а его же `v_get` несёт полный набор с `group#member`. Сузив заодно глаголы, рендер отнял бы живое право у групп — молча, при действующей на вид привязке.
Functions ¶
func CanonicalVerbOrder ¶
CanonicalVerbOrder возвращает позиции канонических глаголов как множество «имя → позиция». Экспортирована ради пробы согласия с набором загрузчика (TestCanonicalVerbOrderAgreesWithTheClassRule): без перечислителя согласие проверялось бы в одну сторону, и шестой глагол загрузчика уехал бы в хвост молча.
func OwnedTypes ¶
func OwnedTypes(resources []catalog.ResourceRow, dsl []byte, module string) []string
OwnedTypes — типы канона, отнесённые закрытой таблицей authzmap.objectTypes к модулю, в порядке имён.
ЗЕРКАЛО TypesOutsideModules: там — канон минус таблица, здесь — канон И таблица. Обе стороны выводятся из ОДНОГО обхода, поэтому «принадлежит модулю» и «не принадлежит никакому» не могут разойтись: тип, выпавший из одной стороны, появляется в другой, а не пропадает.
Пересечение с КАНОНОМ обязательно, а не удобно: запись таблицы, чьего типа в каноне нет, есть расхождение таблицы с моделью, и у него свой держатель — безусловный гейт дрейфа (services/iam/internal/authzmap). Потребовав здесь рендера такого типа, сверка выдала бы находку на предмет соседа и краснела бы до его починки, ничего не говоря о манифесте.
Ключ модуля в таблице и имя модуля закрытого набора — ОДИН словарь, и это замер, а не допущение: модуль балансировки таблица зовёт `loadbalancer`, как и набор, тогда как ТИПЫ его несут префикс `nlb_`. Согласие держит проба TestTheClosedTableSpeaksTheModuleVocabulary — без неё модуль, чей ключ разошёлся бы с набором, потерял бы все свои типы из ожидаемого МОЛЧА, и сверка перестала бы его видеть, оставаясь зелёной.
func Render ¶
Render порождает блок типа модели из ресурса манифеста.
Возвращает байты единицы A: от строки `type X` до последнего перевода строки включительно, БЕЗ завершающей пустой строки — разделитель принадлежит файлу, а не блоку.
func Sweep ¶
Sweep обходит закрытый набор модулей от корня дерева root и сверяет порождённое из манифестов с каноном.
Путь канона НЕ является параметром — это свойство ПОДПИСИ (§2 п. 2, A-03) ¶
Второй операнд сверки резолвится ОТ КОРНЯ ДЕРЕВА по постоянному относительному пути (authzplan) и за пределы этого корня НЕ ВЫХОДИТ. Подменить канон снимком, лежащим рядом со сверщиком, невозможно не по запрету, а потому что ПРЕДЪЯВИТЬ ЕГО НЕЧЕМ: у Sweep нет параметра, куда такой путь встал бы. Держит это компилятор, а не гейт.
ЗДЕСЬ СТОЯЛО «резолвится обходом вверх», и это описывало подъём ДО КОРНЯ ФАЙЛОВОЙ СИСТЕМЫ, снятый задачей #2159. Подъём означал, что канон мог быть найден ВЫШЕ дерева root: перепись печаталась по root, а величины снимались с постороннего файла, и отличить такой зелёный от настоящего было нечем.
Прототип этой задачи был зелен ровно потому, что сверялся со снимком, который нёс два снятых отношения (`define use`, снятое #1115): в дереве их НОЛЬ, в снимке — два. Параметризуемый путь канона вернул бы тот же дефект первой же правкой.
func TypesOutsideModules ¶
func TypesOutsideModules(resources []catalog.ResourceRow, dsl []byte) []string
TypesOutsideModules — типы канона, не отнесённые закрытой таблицей authzmap.objectTypes НИ К ОДНОМУ модулю, отсортированно.
ВЫВОДИТСЯ вычитанием (канон минус значения таблицы), а не выписывается: выписанный перечень устареет молча, и это ровно тот класс, который #1089 ловит.
Такие типы не порождает ни один манифест, и у сверки с ними ровно два негодных исхода: объявить расхождением — гейт красен НАВСЕГДА и его снимут; пропустить молча — шестой блок, дописанный рукой, не заметит НИКТО, а это дословно признак #1089. Годный исход один: перечень объявлен, число печатается переписью, ПРИРОСТ — находка (§2 п. 8, §4 B-06).
Types ¶
type Block ¶
type Block struct {
// Type — имя типа из строки `type X`.
Type string
// Body — байты блока, включая завершающий перевод строки последней строки.
Body []byte
}
Block — блок типа канона: имя типа и его байты (единица A).
func SplitCanon ¶
SplitCanon разбирает канон на блоки в порядке файла.
Порядок — файла, а не имён: канон правят люди, домены в нём сгруппированы, и порядок несёт смысл (§4 B-04). Отдать порядок манифесту значило бы сделать сверку функцией порядка строк YAML.
type Census ¶
type Census struct {
// ModulesInSet — модулей в закрытом наборе платформы.
ModulesInSet int
// ManifestsFound — манифестов найдено под корнем.
ManifestsFound int
// BlocksCompared — блоков сверено побайтово.
BlocksCompared int
// BytesCompared — байт сверено (единица A).
BytesCompared int
// BlocksOutsideModules — блоков канона вне всякого модуля; прирост — находка.
BlocksOutsideModules int
// BlocksOwned — блоков канона, ПРИНАДЛЕЖАЩИХ модулям набора по закрытой
// таблице. Знаменатель для «сверено M»: без него «сверено 0» неотличимо от
// «сверять было нечего».
BlocksOwned int
// Waived — модулей, прощённых ведомостью позаписно.
Waived int
// CanonPath — файл, ИЗ КОТОРОГО сняты величины выше.
//
// Возвращается обходом, а не складывается вызывающим из корня и постоянной
// координаты. Резолв канона знает ДВЕ координаты (канонический файл дерева
// платформы и побайтовую копию, которую модуль везёт с собой), поэтому
// сложенный путь называл бы первую, когда прочитана вторая, — и перепись
// утверждала бы о файле, которого не читала. Тот же класс, ради которого
// корень печатается отдельной строкой.
CanonPath string
}
Census — объём осмотренного. «Ноль находок» обязано быть отличимо от «ноль прочитанного», поэтому величин ПЯТЬ, а не одна: одна скрывает ровно тот случай, ради которого обход заведён.
type Finding ¶
type Finding struct {
// Module — модуль закрытого набора, к которому находка относится.
Module string
// Type — тип модели, если находка про блок; пусто, если про модуль целиком.
Type string
// Side — какая сторона богаче; SideNone, когда находка не про блоки.
Side Side
// Detail — что именно неверно, словами, с первой расходящейся строкой.
Detail string
}
Finding — находка обхода, названная стороной и координатой.
func (Finding) String ¶
String — одна строка отчёта.
Находка БЕЗ модуля не печатает пустого имени. Прежде строка выходила как «модуль : канон не резолвится» — отказ резолва приписывался модулю, которого не назвали, и читатель искал виновника там, где его нет (kacho#2241). Находки без модуля законны: отказ резолва канона и отказ обхода дерева относятся ко ВСЕЙ сверке, а не к чьему-то модулю.
type Side ¶
type Side int
Side — какая сторона сверки оказалась богаче. Два исхода означают РАЗНОЕ для арендатора и чинятся разными правками, поэтому различаются в тексте (§2 п. 3).
type Waiver ¶
type Waiver struct {
// Module — модуль закрытого набора.
Module string
// Issue — номер задачи, под которую выдано послабление. Запись без номера
// есть послабление без предиката снятия: оно не истечёт никогда.
Issue int
}
Waiver — запись ведомости послаблений: модуль набора, чей манифест ещё не приехал.