Documentation
¶
Overview ¶
Package catalog — КАТАЛОЖНЫЙ ФАКТ, прочитанный из СТРОК: какие пары «модуль.ресурс» грантуемы и какие глаголы объявлены каждой.
───────────────────────────────────────────────────────────────────────────── ЧЕМ ЭТОТ ПАКЕТ ОТЛИЧАЕТСЯ ОТ ЛИТЕРАЛА `authzmap`
Литерал `authzmap.objectTypes` / `typeVerbRelations` остаётся и снятию не подлежит: он производная канона `fga_model.fga`, проверяемая гейтом дрейфа, и без него строки каталога остались бы без якоря к канону вовсе. Литерал отвечает на вопрос «что объявлено КАНОНОМ»; этот пакет — на вопрос «что живо В БАЗЕ ПРЯМО СЕЙЧАС».
Пока строки пишет только миграция, ответы совпадают, и совпадают не случайно: страж старта (`seed.AssertCatalogParity`) сверяет литерал с живыми строками на КАЖДОМ старте и при расхождении отказывает в пуске. Различие появляется в один момент — когда строка снята в РАБОТАЮЩЕМ процессе: страж своё уже отработал, а читатель на литерале продолжит считать снятый тип живым до следующего перезапуска. Наблюдаемо это не поломкой, а «прав не выдали» либо, что хуже, выдачей по типу, который платформа сняла.
───────────────────────────────────────────────────────────────────────────── ИМЯ ТИПА МОДЕЛИ ПРИЕЗЖАЕТ СТРОКОЙ — И ЭТО НЕ ВТОРОЙ ПЕРЕХОДНИК
Здесь стояло «имя типа МОДЕЛИ строки каталога не несут — такой колонки в `catalog_resource` нет». Колонка заведена (`object_type`, миграция `20260903112400`), и утверждение снято вместе со своим предметом.
Причина, по которой оно держалось, названа честно и была верна: два места, знающих соответствие, разойдутся тихо. Разошлись бы — если бы соответствие здесь ВЫЧИСЛЯЛОСЬ. Оно не вычисляется: словарь по-прежнему объявляет манифест модуля одной строкой вместе с ресурсом, а колонка есть место, куда он доезжает. Переходник остался один, у него сменилось место хранения.
Согласие колонки с таблицей, порождённой сборкой, держит страж старта (`seed.AssertCatalogParity`) — на КАЖДОМ старте, с отказом в пуске при расхождении.
`cluster` строки по-прежнему не имеет вовсе: вершина иерархии ресурсом не является, и спрашивать её набор у каталога нечем ни при какой колонке.
Index ¶
- Constants
- type Facts
- func (f *Facts) AllVerbVocabulary() []string
- func (f *Facts) CommonVerbVocabulary() []string
- func (f *Facts) FGAObjectType(dotted string) (string, bool)
- func (f *Facts) GrantSelectors(selectors []domain.RuleSelector) []domain.RuleSelector
- func (f *Facts) GrantedVerbs(fgaType string, authored, typeVerbs []string) []string
- func (f *Facts) IsKnownModule(module string) bool
- func (f *Facts) Modules() []string
- func (f *Facts) PublicReadChildren(dotted string) []string
- func (f *Facts) Resources() []ResourceEntry
- func (f *Facts) RetiredResources() []RetiredEntry
- func (f *Facts) RolePreviewLookup() domain.TypeVerbLookup
- func (f *Facts) RoleVerbsFromSelectors(selectors []domain.RuleSelector) []domain.RoleVerb
- func (f *Facts) VerbsOfType(fgaType string) []string
- type Fixed
- type Halves
- type RefreshObserver
- type ResourceEntry
- type ResourceRow
- type RetiredEntry
- type RowSource
- type Rows
- type Snapshot
- type Source
- type VerbRow
Constants ¶
const ( // RefreshOutcomeRefreshed — новое множество прочитано и подменило прежнее. RefreshOutcomeRefreshed = "refreshed" // RefreshOutcomeFailed — обновление не удалось; снимок остался прежним. RefreshOutcomeFailed = "failed" )
Исходы обновления снимка. Набор ЗАКРЫТ и приходит отсюда, никогда из запроса, поэтому кардинальность метрики не растёт с трафиком.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Facts ¶
type Facts struct {
// contains filtered or unexported fields
}
Facts — НЕИЗМЕНЯЕМЫЙ каталожный факт на один момент времени.
Значение собирается один раз и после этого не правится: обновление снимка строит НОВОЕ значение и подменяет указатель. Поэтому вызывающий, взявший факт, держит согласованное множество до конца своего вычисления — половины обновления он не увидит ни при какой конкуренции.
func NewFacts ¶
NewFacts собирает факт из ОБЕИХ половин каталога.
Половины приходят одним значением (`Halves`) и берутся из ОДНОГО снимка: собранный из двух моментов факт показал бы ресурс в обоих перечнях сразу либо ни в одном (kacho#1814).
ПУСТАЯ СНЯТАЯ ПОЛОВИНА ЗАКОННА и означает «снятого нет» — в отличие от живой, пустота которой отвергается ниже. Величина эта платформенная: сегодня снятых строк три при двадцати семи живых, и «ноль» здесь честный ответ, а не признак непрочитанного. Отличать непрочитанное обязан ВЫЗЫВАЮЩИЙ, и единственный, кому это важно, — снимок: он берёт обе половины у порта, который иначе как парой их не отдаёт.
ПУСТОЕ МНОЖЕСТВО ОТВЕРГАЕТСЯ, и это не перестраховка. Пустой снимок отверг бы ВСЕ правила арендатора разом, и снаружи это читалось бы как «продукт сломан», а не как «миграции не применены». На старте до этого не доходит — страж отказывает в пуске раньше, — но обновление снимка идёт БЕЗ стража, и пустой ответ там обязан быть отказом обновления, а не новым снимком.
func (*Facts) AllVerbVocabulary ¶
AllVerbVocabulary — глаголы, которые объявляет ХОТЬ ОДИН живой глагольный тип.
func (*Facts) CommonVerbVocabulary ¶
CommonVerbVocabulary — глаголы, общие ДЛЯ ВСЕХ живых глагольных типов.
func (*Facts) FGAObjectType ¶
FGAObjectType — имя типа МОДЕЛИ ПРАВ для точечного имени каталога («vpc.network» → «vpc_network»), по ЖИВЫМ строкам; ok=false у ресурса, чья строка снята либо которого в каталоге нет вовсе.
Это тот же вопрос, что задавал `authzmap.FGAObjectType`, и та же закрытость: незнакомая пара обязана дать ok=false, а НЕ произвольный тип модели. Отличие одно и оно несущее — ИСТОЧНИК. Порождённая сборкой таблица закрывала направление СНЯТИЯ и не закрывала ЗАВЕДЕНИЕ: тип, которого сборка не знала, получал «не найдено», и вызывающий пропускал его молча при роли, созданной без отказа (kacho#1816, IAM-CT-2-14).
Вторым переходником это не является: соответствие не вычисляется, а читается из строки, куда его положил манифест модуля.
func (*Facts) GrantSelectors ¶ added in v0.5.0
func (f *Facts) GrantSelectors(selectors []domain.RuleSelector) []domain.RuleSelector
GrantSelectors — селекторы с глаголами, которые каждое ПРАВИЛО даёт на каждом своём типе (`RuleSelector.Grants`), по живому каталогу (kaname#878).
Решение о доступе судит глагол ПО ПРАВИЛУ: объект получает глагол только от того правила, чей селектор его выбрал. Проекция роли (`RoleVerbsFromSelectors`) — объединение этих наборов; держать глагол на уровне роли и охват на уровне правила значило бы сочетать глагол одного правила с охватом другого.
Тип, которого живой каталог не знает либо которому правило не даёт ни одного глагола, ключа не получает: «тип есть в охвате, глаголов нет» и «типа нет» для решения одинаковы — глагола нет. Карта при этом НЕ nil и у такого селектора: nil означает «не вычислено», и писатель его отвергает.
Возвращаются КОПИИ: вход вызывающему принадлежит, и портить его заполнением на месте нельзя.
func (*Facts) GrantedVerbs ¶
GrantedVerbs — глаголы, которые правило с авторскими глаголами `authored` даёт НА ТИПЕ `fgaType`.
Предикат ОДИН на обе стороны и живёт у владельца правила (`authzmap.GrantedVerbsWithDeclared`); отсюда приходит единственный факт, который зависит от каталога, — объявляет ли ЖИВОЙ тип набор глаголов вообще. Повторить вычисление здесь значило бы завести второе место об одном предмете: ровно так роль-администратор однажды давала движку всё, а проекции — ничего.
func (*Facts) IsKnownModule ¶
IsKnownModule — членство модуля в ЖИВОМ каталоге. Реализует `domain.ModuleSet`: домен набора не знает и получает его отсюда.
Почему ответ берётся у снимка, а не у запроса к базе ¶
Каталог мал и меняется реже всего в схеме, а спрашивают его на горячем пути создания и правки роли. Запрос на каждом обращении оплачивался бы запросом арендатора. Отставание при этом ОГРАНИЧЕНО и НАЗВАНО — оно равно периоду обновления снимка, задаваемому профилем развёртывания (см. Snapshot), а не сроку жизни процесса: снятие модуля доезжает до пути запроса за один период, без перезапуска.
Подстановочный знак `*` модулем НЕ является: строки с таким именем в каталоге нет и быть не может (`catalog_module_nonempty` плюс грамматика имени), а разрешает его политика правила, а не набор.
func (*Facts) Modules ¶
Modules — ЖИВЫЕ модули каталога, отсортированно. Возвращается КОПИЯ: снимок вызывающему не принадлежит.
func (*Facts) PublicReadChildren ¶ added in v0.5.0
PublicReadChildren — точечные типы, которым передаёт публичное чтение тип `dotted`: дети ТОГО ЖЕ модуля, если тип объявлен контейнером; nil, если не объявлен либо в каталоге его нет.
Ребёнок чужого модуля наследования не получает (Р9 «Способ», допущение автора приёмки): его блок не несёт `public_reader from <указатель>`, и открывать по нему контейнер значило бы писать отношение, которого никто не читает.
func (*Facts) Resources ¶
func (f *Facts) Resources() []ResourceEntry
Resources — ЖИВЫЕ пары каталога в порядке точечного ключа.
Отдаётся КОПИЯ по той же причине, что и у `Modules`: перечень принадлежит неизменяемому факту, и сортировка на месте у вызывающего испортила бы снимок для всех остальных.
func (*Facts) RetiredResources ¶ added in v0.5.0
func (f *Facts) RetiredResources() []RetiredEntry
RetiredResources — СНЯТЫЕ пары каталога в порядке точечного ключа, каждая со своим преемником.
Отдаётся КОПИЯ по той же причине, что у `Resources`: перечень принадлежит неизменяемому факту, и сортировка на месте у вызывающего испортила бы снимок для всех остальных.
Пустой перечень означает «снятого нет» — и означает это ТОЛЬКО у факта, собранного из обеих половин. Факт, которому снятую половину не подали (разовый пересчёт, фикстура), о снятии не высказывается вовсе; единственный читатель этого перечня — витрина разрешений, и она берёт факт у снимка, а снимок получает обе половины позиционно.
func (*Facts) RolePreviewLookup ¶
func (f *Facts) RolePreviewLookup() domain.TypeVerbLookup
RolePreviewLookup — резолв набора глаголов ПАРЫ каталога для превью роли (kacho#1994).
Пара переводится в имя типа МОДЕЛИ по ЖИВОЙ строке, и набор берётся у неё же. Прежде перевод делала таблица, ПОРОЖДЁННАЯ СБОРКОЙ: тип, заведённый применением манифеста в работающем процессе, она не резолвила, и вызывающий брал запасной набор — глаголы ВСЕЙ платформы.
Запасной набор ОСТАЁТСЯ, и он тут же ¶
Правило, не адресующее ни одного типа (форма `*.*` роли-суперпользователя), своего набора не имеет by construction: перечислить ресурсы подстановки домену нечем. Пустое превью читалось бы как «роль ничего не даёт», поэтому такая пара получает ОБЪЕДИНЕНИЕ наборов живых типов.
Объединение, а НЕ пересечение: пересечение сужается, когда какой-нибудь тип снимает у себя глагол, — и роль `*.*` начинала бы обещать меньше, чем даёт, от правки, к ней не относящейся (наблюдалось при kacho#1189).
Оба ответа приходят из ОДНОГО факта: взяв набор типа отсюда, а запасной у другого источника, вызывающий получил бы превью, собранное из двух снимков.
func (*Facts) RoleVerbsFromSelectors ¶
func (f *Facts) RoleVerbsFromSelectors(selectors []domain.RuleSelector) []domain.RoleVerb
RoleVerbsFromSelectors — проекция «тип × глагол» из тех же селекторов, которыми роль материализуется, по ЖИВОМУ каталогу.
Тип в проекции остаётся ТОЧЕЧНЫМ — тем же, каким он назван в селекторах и каким его читает вердикт (`role_verb.object_type`); набор глаголов спрашивается по имени МОДЕЛИ, поэтому перевод делается здесь ровно один раз.
НАПРАВЛЕНИЙ ДВА, и оба обязаны быть верны по живым строкам:
- СНЯТИЕ: тип, чья строка снята, пар не даёт. Пара по снятому типу дошла бы до внешнего ключа `role_verb_type_fk` и была бы им отвергнута, то есть отказ пришёл бы ЧУЖОЙ полосой (IAM-CT-2-06);
- ЗАВЕДЕНИЕ: тип, заведённый применением манифеста в РАБОТАЮЩЕМ процессе, пары даёт. Пока переходник спрашивался у таблицы, порождённой сборкой, этого не было: незнакомый ей тип пропускался молча, и арендатор не получал ничего при роли, созданной без отказа (IAM-CT-2-14).
Порознь каждое направление выполнимо портом, который не производит пар НИКОГДА, — поэтому утверждаются оба.
Проекция роли есть ОБЪЕДИНЕНИЕ глаголов её правил (`GrantSelectors`): одно вычисление на обе стороны, и разойтись роль с правилом не может.
func (*Facts) VerbsOfType ¶
VerbsOfType — ГЛАГОЛЫ, объявленные ЖИВЫМ типом, отсортированно; nil у типа, чья строка снята либо которого в каталоге нет вовсе (`cluster`).
Возвращается КОПИЯ: значение снимка вызывающему не принадлежит, а испортить его он мог бы одной сортировкой на месте.
type Fixed ¶
type Fixed struct{ F *Facts }
Fixed — источник, отдающий один и тот же факт. Нужен там, где обновления не бывает по построению: разовому пересчёту на старте и пробам.
type Halves ¶ added in v0.5.0
type Halves struct {
// Live — ЖИВОЕ множество: строки, по которым выдача принимается.
Live Rows
// Retired — СНЯТОЕ множество: строки, у которых `retired_at` проставлен.
// Пустое множество законно и означает «снятого нет».
Retired Rows
}
Halves — ОБЕ половины каталога, прочитанные ОДНИМ снимком.
Половины приходят вместе и позиционно, а не одна с необязательной второй: половина, которую можно забыть, и была тем дефектом, ради снятия которого тип заведён — свежезапущенный процесс отдавал арендатору ПУСТОЙ перечень снятого, и отличить это от «ничего не снято» было нечем (kacho#1814).
СОБИРАТЬ ЗНАЧЕНИЕ ИЗ ДВУХ ЧТЕНИЙ НЕЛЬЗЯ: снимок, взятый из разных моментов, показал бы состояние, которого в базе не бывает ни при каком порядке применения, — ресурс в ОБЕИХ половинах сразу либо НИ В ОДНОЙ. Порядок чтения от этого не спасает: он убирает ровно одно из двух состояний и заводит второе. Снимает оба общая транзакция, и это свойство построения, а не проверки.
type RefreshObserver ¶
type RefreshObserver interface {
IncCatalogSnapshotRefresh(outcome string)
}
RefreshObserver — порт наблюдения за обновлением снимка.
Счётчика ДВА, и одного не хватает ни на один из двух вопросов. Ноль отказов даёт и исправный процесс, и процесс, ни разу не пошедший обновляться, — то есть путь, умерший целиком, выглядел бы здоровее всех. Растущий ноль удавшихся при этом не говорит, отказывало ли обновление или его не запускали. Знаменатель считается наравне с числителем.
type ResourceEntry ¶
type ResourceEntry struct {
Module string
Resource string
// ObjectType — имя типа в словаре МОДЕЛИ ПРАВ (`vpc_network`, `account`).
ObjectType string
}
ResourceEntry — одна ЖИВАЯ пара каталога вместе с именем её типа в словаре МОДЕЛИ ПРАВ.
Три величины отдаются ВМЕСТЕ намеренно. Имя типа не выводится из пары (правила `<модуль>_<ресурс>`, верного на всех строках, не существует — см. `ResourceRow.ObjectType`), поэтому читатель, получивший пару без имени, пошёл бы за ним к словарю, ПОРОЖДЁННОМУ СБОРКОЙ, — то есть ровно туда, откуда его уводит этот перечень.
type ResourceRow ¶
type ResourceRow struct {
Module string
Resource string
// ObjectType — имя типа МОДЕЛИ ПРАВ (`vpc_network`, `account`), которым
// адресуется отношение `v_<глагол>`.
//
// Оно ПРИЕЗЖАЕТ строкой, а не выводится из пары, и это несущее решение, а не
// удобство: правила вывода `<модуль>_<ресурс>` в дереве нет — у `storage` и
// `registry` имя ресурса множественное, а тип единственного числа
// (`storage.volumes` → `storage_volume`), у ярусных предков иерархии тип
// идёт БЕЗ приставки модуля (`iam.account` → `account`). Выражения, верного
// на всех строках, не существует.
//
// Без него читатель спрашивал бы имя у словаря, ПОРОЖДЁННОГО СБОРКОЙ, и тип,
// заведённый применением манифеста в работающем процессе, получал бы «не
// найдено» — то есть пропускался бы молча (kacho#1816, IAM-CT-2-14). Это второе
// направление того же предмета, что и снятие: снятие строкам видно, потому
// что строка исчезает; заведение видно только если строка несёт ВСЁ, что о
// ресурсе нужно знать.
ObjectType string
// SupersededBy — точечное имя ЖИВОГО ресурса взамен снятого
// (`compute.disk` → `storage.volumes`); пусто у ЖИВОЙ строки и у снятой,
// преемник которой не назван.
//
// Пустое означает «преемник не назван», и отличать это от «преемник —
// пустая строка» обязан читатель по САМОМУ полю, а не по длине перечня:
// снятая строка без преемника остаётся снятой и обязана быть названа.
// Пустое значение, означающее что-то иное, чем «пусто», есть утверждение о
// ресурсе, которого никто не делал.
//
// У живой строки значение невыразимо В БАЗЕ —
// `CHECK catalog_resource_successor_only_when_retired`, — поэтому живая
// половина колонку и не выбирает: читать нечего by construction.
SupersededBy string
// ParentTypes — типы МОДЕЛИ ПРАВ, на которые указывают указатели ресурса
// (`parents` манифеста), в порядке манифеста: у `registry.repositories` —
// `registry_registry`, у `registry.registries` — `project`.
//
// Колонка `catalog_resource.parent_types`. Пусто у строки, которую не
// объявил доставленный манифест (посев литерала): тогда связь «ребёнок →
// контейнер» каталогу неизвестна, и публичное чтение через неё не
// открывается — отсутствие связи закрывает, а не открывает.
ParentTypes []string
// PublicReadToChildren — тип объявлен контейнером публичного чтения
// (`publicReadToChildren: true` манифеста, kaname#856). Колонка
// `catalog_resource.public_read_to_children`.
PublicReadToChildren bool
}
ResourceRow — одна ЖИВАЯ строка `kaname.catalog_resource`.
Точечная форма (`dotted`) в структуре не хранится: она производна от пары и собирается там, где нужна. Хранить её третьим полем значило бы завести место, где она может разойтись с парой, — а согласие этих двух в базе держит проверка колонки, а не читатель.
func (ResourceRow) SameForm ¶ added in v0.5.0
func (r ResourceRow) SameForm(o ResourceRow) bool
SameForm — строки описывают ресурс одинаково по ВСЕМ величинам, которые применение пишет: имя типа модели, указатели и признак контейнера.
Одно место на всех, кто спрашивает «изменилась ли строка» (план применения и перепись записанного): разойдясь, они дали бы оператору план «без изменений» при применении, которое строку перепишет.
type RetiredEntry ¶ added in v0.5.0
type RetiredEntry struct {
Module string
Resource string
// SupersededBy — точечное имя ЖИВОГО ресурса взамен снятого; пусто означает
// «преемник не назван», а не «преемник — пустая строка».
SupersededBy string
}
RetiredEntry — одна СНЯТАЯ пара каталога и её преемник.
Глаголов запись НЕ несёт, и это решение, а не пропуск: строка `catalog_verb` снята вместе с ресурсом, спрашивать набор не у кого, а выдать глаголы ПРЕЕМНИКА под именем снятого значило бы утверждать совпадение наборов, которого никто не проверял.
type RowSource ¶
RowSource — ПОРТ чтения каталога. Объявлен здесь, у потребителя; реализация — `repo/kaname/pg/catalog_repo.go`.
Метод ОДИН и отдаёт обе половины, потому что снимок обязан быть один. Порт из двух методов позволял бы вызывающему спросить только живую — то есть завести ровно ту форму, которой снятый ресурс до арендатора не доезжал.
type Rows ¶
type Rows struct {
Modules []string
Resources []ResourceRow
Verbs []VerbRow
}
Rows — ЖИВОЕ множество каталога, прочитанное одним обращением.
Все три перечня приходят ВМЕСТЕ и от одного чтения. Разнести их по трём вызовам значило бы допустить снимок, собранный из разных моментов времени: ресурс уже снят, а его глаголы ещё живы — состояние, которого в базе не бывает ни при каком порядке применения.
type Snapshot ¶
type Snapshot struct {
// contains filtered or unexported fields
}
Snapshot — держатель каталожного факта в процессе.
Почему СНИМОК, а не запрос на пути запроса ¶
Каталог спрашивают на горячих путях — создание и правка роли, пересчёт проекции, сборка кортежей, — а сам он мал (десятки ресурсов, сотня глаголов) и меняется реже всего в схеме. Запрос к базе на каждом обращении оплачивался бы запросом арендатора; снимок платит один раз за период.
Отставание ОГРАНИЧЕНО и НАЗВАНО ¶
Величина отставания — период обновления, и он задаётся профилем развёртывания, а не вшит: вшитая величина есть решение, принятое за оператора и не предъявленное ему.
Отказ обновления НЕ фатален и НЕ тих ¶
Ронять службу из-за отставшего снимка значило бы менять ограниченное отставание на полный отказ. Но и молчать нельзя: контроль, о чьих отказах никто не узнаёт, неотличим от отсутствующего. Отсюда пара — запись журнала уровня `Error` и счётчик.
func NewSnapshot ¶
func NewSnapshot(initial Halves, src RowSource, log *slog.Logger, obs RefreshObserver) (*Snapshot, error)
NewSnapshot заполняет снимок УЖЕ ПРОЧИТАННЫМИ половинами.
Строки приходят параметром, а не читаются здесь, и это несущее решение, а не удобство: на старте их уже прочитал страж паритета — ОБЕ половины, — и второе чтение об одном предмете дало бы два места, которые разойдутся молча. Проба `IAM-CT-2-01` утверждает это РАВЕНСТВОМ: операторов к таблицам каталога за время старта ровно столько, сколько шлёт сам страж.
Почему ОБЕ половины, а не одна живая ¶
Снятую половину страж читает и сегодня — и выбрасывает, оставляя одни числа. Пока снимок брал только живую, свежезапущенный процесс отдавал арендатору ПУСТОЙ перечень снятого до первого обновления, то есть целое окно отвечал «платформа ничего не снимала» на вопрос, ради которого к нему и пришли (kacho#1814, IAM-SUC-11). Донести уже прочитанное стоит НУЛЯ новых операторов — прочитать заново стоило бы шести и покрасило бы `IAM-CT-2-01`.
Заполнение НЕ считается обновлением: зачти его — и «ноль обновлений за жизнь процесса» станет неотличимо от одного, то есть счётчик перестанет отвечать на вопрос, ради которого заведён.
func (*Snapshot) Refresh ¶
Refresh перечитывает ОБЕ половины каталога и подменяет факт целиком.
Половины приходят ОДНИМ снимком порта: собранные из двух моментов, они дали бы арендатору ресурс в обоих перечнях сразу либо ни в одном — состояние, которого в базе не бывает ни при каком порядке применения (kacho#1814, IAM-SUC-09).
Подмена атомарна: вызывающий, взявший факт до неё, доработает на прежнем согласованном множестве, а не увидит половину обновления.
Отказ ОСТАВЛЯЕТ ПРЕЖНИЙ снимок — включая случай пустого ответа без ошибки: пустой снимок отверг бы все правила арендатора разом, и это читалось бы как «продукт сломан», а не как «условие не создано».
func (*Snapshot) Run ¶
РЕПЛИКИ: на-реплику — петля обслуживает снимок СВОЕГО процесса: каталожный факт живёт в памяти реплики, и обновить его за соседа нельзя by construction. Общего состояния она не трогает, в базу только ЧИТАЕТ, поэтому N реплик дают N одинаковых чтений закрытого каталога — цена, ограниченная числом реплик и периодом, а не нагрузкой арендатора.
Run обновляет снимок с назначенным периодом и завершается по отмене контекста.
Отказ отдельного круга цикл НЕ прерывает: он уже сообщён журналом и счётчиком, а прекращение обновлений превратило бы ограниченное отставание в бессрочное.
type Source ¶
type Source interface {
Facts() *Facts
}
Source — читатель каталожного ФАКТА, каким его видит use-case: он знает, что факт откуда-то приходит, и не знает откуда.
Возвращается ЗНАЧЕНИЕ на момент вызова, а не держатель: обновление снимка подменяет указатель целиком, поэтому вызывающий, взявший факт один раз, работает на согласованном множестве до конца своего вычисления и не увидит половину обновления.
type VerbRow ¶
type VerbRow struct {
Module string
Resource string
// Verb — каноническая форма БЕЗ приставки отношения, та же, какой говорит
// `role_verb.verb`.
Verb string
// PerObject — строка производит пообъектное отношение `v_<verb>`. Ложь
// означает глагол, законный АВТОРСКИ и не дающий кортежа ни на одном
// объекте: его действие несёт ярус на родителе (задача kacho#1863).
//
// Читателю этот признак НЕ безразличен, и в этом весь его смысл: набор
// глаголов типа обязан оставаться пообъектным, потому что по нему
// материализуются кортежи. Пропустить сюда ярусную строку значило бы вернуть
// снятое отношение целиком.
PerObject bool
}
VerbRow — одна ЖИВАЯ строка `kaname.catalog_verb`.