modelrender

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: 14 Imported by: 0

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

View Source
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).
	//
	// «Не выполнилось» не вычитается из вердикта, не зачитывается в успех и не
	// выдаётся за находку (`testing.md` §«Чтение вердикта»).
	//
	// ─────────────────────────────────────────────────────────────────────────
	// ЧЕМ ЭТО ОТЛИЧАЕТСЯ ОТ НАСТОЯЩЕЙ ПРОПАЖИ КАНОНА
	//
	// Различает ОДИН факт: есть ли в судимом дереве каталог контрактов вообще.
	// Нет каталога — контракты не поставлены, условие не создано. Каталог есть,
	// а канона в нём нет — это находка: его удалили либо он переехал, и молчать
	// об этом нельзя.
	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

View Source
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

func CanonicalVerbOrder() map[string]int

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

func Render(r manifest.Resource) ([]byte, error)

Render порождает блок типа модели из ресурса манифеста.

Возвращает байты единицы A: от строки `type X` до последнего перевода строки включительно, БЕЗ завершающей пустой строки — разделитель принадлежит файлу, а не блоку.

func Sweep

func Sweep(resources []catalog.ResourceRow, root string, waivers []Waiver) (Census, []Finding, int)

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

func SplitCanon(dsl []byte) []Block

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 — объём осмотренного. «Ноль находок» обязано быть отличимо от «ноль прочитанного», поэтому величин ПЯТЬ, а не одна: одна скрывает ровно тот случай, ради которого обход заведён.

func (Census) String

func (c Census) String() string

String печатает перепись словами: перечень величин, читаемый человеком в логе прогона. Печатается ВСЕГДА, включая VOID, — иначе «ноль находок» неотличимо от «ноль прочитанного».

type Finding

type Finding struct {
	// Module — модуль закрытого набора, к которому находка относится.
	Module string
	// Type — тип модели, если находка про блок; пусто, если про модуль целиком.
	Type string
	// Side — какая сторона богаче; SideNone, когда находка не про блоки.
	Side Side
	// Detail — что именно неверно, словами, с первой расходящейся строкой.
	Detail string
}

Finding — находка обхода, названная стороной и координатой.

func (Finding) String

func (f Finding) String() string

String — одна строка отчёта.

Находка БЕЗ модуля не печатает пустого имени. Прежде строка выходила как «модуль : канон не резолвится» — отказ резолва приписывался модулю, которого не назвали, и читатель искал виновника там, где его нет (kacho#2241). Находки без модуля законны: отказ резолва канона и отказ обхода дерева относятся ко ВСЕЙ сверке, а не к чьему-то модулю.

type Side

type Side int

Side — какая сторона сверки оказалась богаче. Два исхода означают РАЗНОЕ для арендатора и чинятся разными правками, поэтому различаются в тексте (§2 п. 3).

const (
	// SideNone — расхождение не про блоки (нет манифеста, ведомость без предмета).
	SideNone Side = iota
	// SideRenderedBeyondCanon — порождено сверх канона: РАСШИРЯЕТ доступ.
	SideRenderedBeyondCanon
	// SideCanonBeyondRendered — канон сверх порождённого: ТЕРЯЕТ доступ.
	SideCanonBeyondRendered
)

func (Side) String

func (s Side) String() string

String называет сторону словами вызывающего, а не номером.

type Waiver

type Waiver struct {
	// Module — модуль закрытого набора.
	Module string
	// Issue — номер задачи, под которую выдано послабление. Запись без номера
	// есть послабление без предиката снятия: оно не истечёт никогда.
	Issue int
}

Waiver — запись ведомости послаблений: модуль набора, чей манифест ещё не приехал.

Jump to

Keyboard shortcuts

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