authzmap

package
v0.3.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: 5 Imported by: 0

Documentation

Overview

fga_types.go — closed (module, resource) → fga_object_type table.

RBAC v2. Every concrete-resourceName permission emits a direct per-object FGA tuple at the type returned here. Wildcard- resourceName permissions emit a tier tuple at the binding's scope anchor instead, and the returned ok is false when the pair is unknown — the caller falls back to the scope-anchor.

ОБЕ таблицы типов пакета ПОРОЖДАЮТСЯ из манифестов модулей и лежат в `tables_gen.go`: и набор действий каждого типа (`typeVerbRelations`), и словарь имён каталога (`objectTypes`). Завести ресурс — значит вписать его в `resources` манифеста его модуля; правка Go для этого не требуется, и разбор переноса стоит внизу этого файла, там, где жил литерал.

Тип обязан быть объявлен в канонической модели `proto/kaname/cloud/iam/v1/fga_model.fga` в такт с манифестом: безусловный гейт дрейфа (fga_model_drift_test.go) роняет сборку на расхождении в любую сторону. Таблица намеренно ЗАКРЫТА — неизвестная пара обязана дать ok=false, а не произвольный тип модели.

─── The resource name in the table is SINGULAR, and the permission token's is PLURAL.

objectTypes key    vpc.gateway            (the catalog dictionary)
permission token   vpc.gateways.get       (proto authz annotation → catalog)

This divergence is DELIBERATE, not drift, and must not be "reconciled" by pluralizing these keys. The two names have different referents:

  • the key names an FGA OBJECT TYPE — the single object a tuple is written on. The canonical model declares those types in the singular (`type vpc_gateway` in fga_model.fga), and the unconditional drift-gate above requires the table to agree with it EXACTLY. Pluralizing a key would either desynchronize the table from the model or force renaming the model's types — a change to the authorization model, not naming hygiene;
  • the permission token names an ACTION ON A COLLECTION and mirrors the REST collection path it is annotated next to (`/vpc/v1/gateways`). Its resource segment is required to be plural, pluralized exactly once; that rule is held over the .proto annotations by internal/repohygiene TestVpcPermissionTokenPluralizedExactlyOnce.

The two are never compared, so the difference costs nothing. Neither of the two callers of ObjectType feeds a catalog token's resource segment in here: permission_catalog/list_catalog.go iterates Catalog() — this table's OWN keys — and permissions_to_relations.go matches a ROLE's permission patterns, where a plural resource segment does not resolve and the caller takes the documented scope-anchor fallback (that is the "ok is false when the pair is unknown" branch named above, and it behaves identically for `vpc.subnets` and for a misspelled `vpc.subnetses`).

Package authzmap maps a role's permissions[] to FGA relations and owns the closed (module,resource)→fga_object_type table. Adapter-free (stdlib only): consumed from AccessBinding tuple-emission paths without coupling to internal/repo or internal/clients.

permissions_to_relations.go — single mapper from a role's permissions[] to a deduplicated set of FGA relations.

Replaces the name-based name→relation collapse that lived in `internal/apps/kaname/api/access_binding/tuples.go::roleNameToRelation`.

Strategy (conservative — granular FGA model is out of scope for this mapper):

  1. nil / empty permissions → []Relation{"viewer"} (least privilege).

  2. Group permissions by verb-class (read-only / write / admin), pick the STRONGEST tier present:

    read-only verbs : get | list | view | watch | describe → "viewer" write verbs : create | update | delete | write | patch | put → "editor" admin / wildcard: admin | * | manage → "admin"

Это НЕ пер-RPC карта прав, и вывести её из аннотаций нельзя (замерено 2026-08-10)

Файл дважды принимали за «рукописную карту прав iam», которую осталось перевести на вывод из аннотаций дескрипторов (`pkg/authz/catalogderive`), как сделано у шести сервисов. Это ошибка опознания, и стоит она дорого: перевод «по аналогии» подменил бы один словарь другим и поехал бы молча — оба ведь выглядят как `модуль.ресурс.глагол`.

Здесь отображается РОЛЬ (её `permissions[]`) в ярус отношения FGA при эмиссии кортежей AccessBinding. Пер-RPC карта отвечает на другой вопрос — «что проверить перед ЭТИМ вызовом» — и у iam её нет вовсе: iam не перепроверяет конечного пользователя на своих слушателях, он и есть точка решения (`internal/authzguard/interceptor.go`).

Предикаты, которыми это установлено, — повторяйте их, а не верьте строке:

  • перепись объявлений типа карты прав из `pkg/authz` по не-тестовым файлам под `services/` даёт 5 файлов в 4 сервисах (vpc·compute·nlb·registry); в iam — НОЛЬ.

    ОСТОРОЖНО, предикат ТЕКСТОВЫЙ: он считает и упоминания в комментариях. Проверено на себе — первая редакция этого абзаца выписала имя типа дословно, и перепись немедленно выросла до 6 файлов в 5 сервисах, показав «карту у iam» ровно там, где абзац утверждал её отсутствие. Поэтому имя здесь названо описательно, а считать надо по ИСПОЛНЯЕМОЙ части (разбор AST либо `grep -v` по этому файлу);

  • словари не пересекаются ВООБЩЕ: у аннотаций 253 уникальных строки разрешения (`iam.cluster_admins.grant`, `vpc.networks.get` — третий сегмент это ГЛАГОЛ RPC), у ролей 55 (`compute.disk.edit` — третий сегмент это ЯРУС), пересечение — 0. Вывести отсюда нечего: аннотации не содержат ни одной строки, которую этот маппер принимает на вход.

Отсюда правило для следующего захода: «у iam осталась рукописная карта» — утверждение о пер-RPC карте прав, а не об этом файле. Если однажды iam заведёт пер-RPC карту, она будет выведена из аннотаций и будет жить рядом с его рубежами — этот маппер останется на месте, потому что его предмет другой.

type_dictionaries.go — ДВА СЛОВАРЯ ИМЁН ТИПА И ЕДИНСТВЕННЫЙ ПЕРЕХОДНИК МЕЖДУ НИМИ.

───────────────────────────────────────────────────────────────────────────── ЧТО ЗА ДВА СЛОВАРЯ

Один и тот же тип ресурса назван в iam ДВУМЯ разными именами, и оба законны:

словарь МОДЕЛИ ПРАВ    vpc_network · account · project · cluster
словарь КАТАЛОГА       vpc.network · iam.account · iam.project · (нет)

Словарь модели — вокабуляр `fga_model.fga`: им приходит вопрос о доступе, им говорит компилятор плана, и ТОЛЬКО он умеет назвать `cluster` — вершину иерархии, которой в каталоге ресурсов нет вовсе, потому что кластер не ресурс.

Словарь каталога — вокабуляр разрешений (`<модуль>.<ресурс>`): им адресуют объекты правила ролей, им же названы типы в проекциях, выведенных из ролей.

───────────────────────────────────────────────────────────────────────────── ЧТО ЭТИМИ ФУНКЦИЯМИ ДЕРЖИТСЯ

У КАЖДОЙ колонки, несущей имя типа, ровно один словарь, и он объявлен:

словарь модели    relation_fact.object_type · access_bindings.resource_type ·
                  resource_parent_edge.object_type · resource_parent_edge.parent_type
словарь каталога  resource_mirror.object_type · role_verb.object_type ·
                  role_rule_selectors.object_types

Соединение колонок РАЗНЫХ словарей не совпадает НИКОГДА и молча: исход выглядит как «права нет», а не как ошибка. Поэтому перевод делается один раз, на границе — там, где значение уезжает в чужой словарь, — и делается ОТСЮДА. Второго переходника в дереве не заводится: два места, знающих соответствие, разойдутся, и разойдутся тихо.

───────────────────────────────────────────────────────────────────────────── ПОЧЕМУ ОБЕ ТОТАЛЬНЫ, А НЕ ОТДАЮТ ok

Пара `(значение, известен ли)` у вызывающего немедленно сворачивается в «неизвестный — как есть»: тип вне каталога законен (зеркало держит непрозрачный тип чужого сервиса как есть), и превращать его в пустую строку нельзя — пустая подстановка совпала бы с пустым значением колонки и превратила бы «типа не знаем» в «совпало». Свернуть это здесь значит не дать вызывающему свернуть иначе. Кому нужна сама принадлежность каталогу — зовёт `DottedType` / `FGAObjectType` и читает `ok`.

ОБЕ ИДЕМПОТЕНТНЫ, и это свойство словарей, а не удача: имя каталога содержит точку всегда, имя модели — никогда (в имени типа модель допускает только буквы, цифры, `_` и `-`). Пересечения у словарей нет, поэтому повторный перевод уже переведённого имени возвращает его же. На этом свойстве стоят проверки схемы `*_type NOT LIKE '%.%'` у колонок словаря модели: регрессия писателя отвергается строкой, а не перестаёт совпадать молча.

Index

Constants

View Source
const VerbRelationPrefix = "v_"

VerbRelationPrefix — приставка имени глагольного отношения модели. Она же — форма, в которой глагол попадает в кортеж.

Variables

This section is empty.

Functions

func AllVerbVocabulary

func AllVerbVocabulary() []string

AllVerbVocabulary — ГЛАГОЛЫ (без приставки `v_`), которые объявляет ХОТЬ ОДИН глагольный тип, то есть ОБЪЕДИНЕНИЕ их наборов, отсортированно.

ЭТО И ЕСТЬ «ВСЕ ГЛАГОЛЫ ПЛАТФОРМЫ» — величина, которой у нас не было, пока наборы типов совпадали. Тогда пересечение, набор любого типа и объединение были одним и тем же числом, и вызывающему, которому нужно «всё», доставалось `CommonVerbVocabulary` — по совпадению, а не по существу.

РАЗЛИЧИЕ СТАЛО НАБЛЮДАЕМЫМ И СТОИЛО БЫ ДОРОГО. Пересечение объявлено СУЖАЮЩИМСЯ (см. его комментарий и поле `closed_verbs` каталога): снял тип у себя глагол — пересечение стало короче. Якорь привязки, у которого СВОЕГО набора нет (кластер), разворачивает подстановку `*` запасным набором, а ярус выводится из развёрнутых глаголов, — значит на пересечении роль-суперпользователь молча понижалась бы с администратора до наблюдателя при сужении набора у ЧУЖОГО типа. Найдено при снятии `v_delete` с `iam_user` (#1189), когда пересечение стало `[get list]`; до этого его спасал `delete`, оставшийся у всех.

Пересечение и объединение — РАЗНЫЕ вопросы, и путать их нельзя: «что даёт ЛЮБОЙ ресурс» против «что бывает вообще». Первый спрашивает публичное поле каталога, второй — запасной набор для якоря без собственного.

func CatalogKeys

func CatalogKeys() []string

CatalogKeys — дотированные ключи закрытой таблицы, отсортированные. Отличается от Catalog() только формой (строка вместо пары) и заведена ради переписей, которым пара не нужна.

func CatalogResourcesWithoutOwnService

func CatalogResourcesWithoutOwnService() map[string]string

CatalogResourcesWithoutOwnService отдаёт КОПИЮ перечня грантуемых ресурсов без собственной службы: дотированный ключ → причина.

func CatalogSeedModules

func CatalogSeedModules() []string

CatalogSeedModules — модули каталога: РАЗЛИЧНЫЕ приставки грантуемых пар, отсортированные.

Это третья половина посева, а не второй словарь

Строк каталога три вида, и у всех трёх производитель обязан быть один (см. шапку файла). Модульная половина до задачи #1927 приходила из отдельного литерала домена `knownModules` — второго места об одном предмете, за согласием которого следил отдельный гейт дрейфа. Здесь она ВЫВОДИТСЯ из того же `objectTypes`, из которого выведены ресурсы, поэтому состояние «модуль объявлен, а грантуемых пар у него ни одной» невыразимо by construction, а не отвергается проверкой.

Это ЛЕВАЯ сторона паритета — на пути запроса её не спрашивают

Как и у двух соседей ниже, ответ здесь — «что объявлено КАНОНОМ», а не «что живо в базе прямо сейчас». Живое множество меняется снятием строки в РАБОТАЮЩЕМ процессе, поэтому членство модуля на пути запроса спрашивают у строк (`catalog.Facts.IsKnownModule`), а этот перечень служит стражу старта левой стороной сверки и оснастке дерева, у которой базы нет by construction.

Порядок — сортировка, а не «канонический порядок платформы»

Снятый литерал объявлял порядок «iam первым, затем ресурсные домены» и держал его рукой. Порядок ниже детерминирован построением: перечень выводится сортировкой, и разойтись с самим собой между двумя вызовами не может.

func CatalogSpelling

func CatalogSpelling(module, derived string) string

CatalogSpelling приводит написание РЕСУРСА, ВЫВЕДЕННОЕ из имени службы, к написанию, которым его называет ключ закрытой таблицы типов (модуль не возвращается: он у обеих сторон один и тот же). Написание, о котором расхождение не объявлено, возвращается как есть — совпадение это общий случай, и запись о нём была бы записью без предмета.

Вызывающий — привязка записей каталога; сама таблица типов через неё НЕ резолвится, поэтому словарь ключей `ObjectType` не расширяется ни на один вход. Расширить его значило бы принять в правиле роли написание, которое validateRuleCatalog на пути запроса отвергает, — то есть объявить возможность, которой нет.

func CatalogSpellingDivergences

func CatalogSpellingDivergences() map[string]string

CatalogSpellingDivergences отдаёт КОПИЮ объявленных расхождений (выведенное → ключ таблицы), для переписей и гейтов.

func CatalogTypeName

func CatalogTypeName(typeName string) string

CatalogTypeName — имя типа в словаре КАТАЛОГА.

Тип вне каталога модели возвращается КАК ЕСТЬ (см. «почему тотальна» выше).

ЧИТАТЕЛЮ КОЛОНКИ СЛОВАРЯ КАТАЛОГА ЭТОГО НЕ ХВАТАЕТ (kacho#1986)

Зеркальная граница к той, что названа у `ModelTypeName` ниже, и исход у неё ТИШЕ. Таблица под этой функцией ПОРОЖДЕНА СБОРКОЙ (`tables_gen.go` из манифестов дерева), а функция ТОТАЛЬНА. Значит тип, заведённый применением манифеста в РАБОТАЮЩЕМ процессе, она вернёт именем МОДЕЛИ — и это уедет в сравнение с колонкой, названной словарём каталога. Ни одна строка не совпадёт, отказа не будет: перечисление отдаст пусто, селектор меток объект не покроет, а клиент прочтёт это как «прав нет».

Соединяешь колонку словаря каталога — спрашивай имя у ЖИВОЙ СТРОКИ каталога (`catalog_resource.dotted` по `object_type`; образец — `relverdict/catalogtype.go`, где перевод стоит выражением в самом запросе и потому не стоит ни лишнего круга к базе, ни окна между чтениями). Здесь остаются вызывающие, которым живая строка недоступна by construction и чей вход — только посеянные типы.

func CommonVerbVocabulary

func CommonVerbVocabulary() []string

CommonVerbVocabulary — ГЛАГОЛЫ (без приставки `v_`), общие ДЛЯ ВСЕХ глагольных типов, то есть ПЕРЕСЕЧЕНИЕ их наборов, отсортированно.

Именно это значение проецируется публичным полем каталога прав. Пока все типы несут один набор, пересечение равно ему. С появлением типа с расширенным набором пересечение сузится — и это верное поведение поля: оно объявлено как набор, общий для всех ресурсов, а не как перечень всех существующих глаголов.

func DottedType

func DottedType(fgaType string) (string, bool)

DottedType maps a rights-model object_type (e.g. "compute_instance") back to the dotted closed-table key (e.g. "compute.instance") used by role_rule_selectors and resource_mirror.object_type. ok=false when the FGA type is not in the closed table — callers may then fall back to the FGA type verbatim (the mirror keeps a generic opaque object_type). Reverse of ObjectType.

func FGAObjectType

func FGAObjectType(dotted string) (string, bool)

FGAObjectType resolves the rights-model object_type for a dotted closed-table key ("vpc.securityGroup" → "vpc_security_group", "iam.account" → "account"). It is the single canonical dotted→FGA-type mapping (SplitObjectType on the FIRST dot, then ObjectType over the closed table) shared by every FGA-object derivation — the reconciler's tuple builder and the verify-gate's ledger lookup both route through it so their object keys cannot drift. ok=false when the dotted key is not in the closed table (callers must NOT fall back to a hand-rolled substitution — an unknown type must surface as ok=false, never as an arbitrary FGA type).

func GrantedVerbs

func GrantedVerbs(fgaType string, authored, typeVerbs []string) []string

GrantedVerbs — глаголы, которые правило с авторскими глаголами `authored` даёт НА ТИПЕ `fgaType`, в канонической форме и без приставки отношения.

typeVerbs — набор, по которому разворачивается подстановка и отсеивается чужой глагол. Он ПАРАМЕТР, а не выводится здесь: у вызывающего бывает причина предъявить урезанный набор (отрицательная проба обязана уметь показать тип, у которого глагола нет, ДО того как такой тип появится в каталоге).

Тип, у которого набор глаголов не ОБЪЯВЛЕН вовсе, не получает ни одного глагола, каким бы ни был `typeVerbs`: отношения `v_*` у него не существует, и пара в проекции адресовала бы отношение, которого нет в модели.

func GrantedVerbsWithDeclared

func GrantedVerbsWithDeclared(fgaType string, declaresVerbs bool, authored, typeVerbs []string) []string

GrantedVerbsWithDeclared — ТОТ ЖЕ предикат, но единственный факт, который он берёт у каталога, приходит ПАРАМЕТРОМ: объявляет ли тип набор глаголов вообще.

Разделение заведено задачей #1816. Каталог существует ДВУМЯ выражениями — литералом (производная канона `fga_model.fga`) и живыми строками `kaname.catalog_*`, — и ответ на «объявлен ли набор» у них расходится ровно в один момент: когда строка снята в РАБОТАЮЩЕМ процессе. Вычисление же глаголов от источника не зависит вовсе, поэтому оно и осталось ОДНИМ: две реализации вопроса «что роль разрешает на типе» по отдельности непротиворечивы и расходятся молча — так роль-администратор давала движку весь набор, а проекции `role_verb` ни одного глагола (#496).

`declaresVerbs` и `typeVerbs` — РАЗНЫЕ величины, и свести их нельзя: вызывающий вправе предъявить набор ЗАПАСНОЙ (якорь без собственного набора разворачивает подстановку общим словарём ради вывода яруса), и тогда `typeVerbs` непуст, а тип не объявляет ничего.

func IsExpandableRelation

func IsExpandableRelation(relation string) bool

IsExpandableRelation reports whether `relation` is in the closed set of relations ExpandAccess accepts (see expandableRelations). An unknown relation must be rejected by the caller with INVALID_ARGUMENT (no probing of arbitrary FGA relation strings).

func IsHierarchyScopeType

func IsHierarchyScopeType(fgaType string) bool

IsHierarchyScopeType — тип-область иерархии (аккаунт, проект).

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

func ModelTypeName

func ModelTypeName(typeName string) string

ModelTypeName — имя типа в словаре МОДЕЛИ ПРАВ.

Тип вне каталога возвращается КАК ЕСТЬ — в том числе `cluster`, которого в каталоге нет и быть не должно.

ПИСАТЕЛЮ КОЛОНКИ СЛОВАРЯ МОДЕЛИ ЭТОГО НЕ ХВАТАЕТ (kacho#1982)

Таблица под этой функцией ПОРОЖДЕНА СБОРКОЙ (`tables_gen.go` из манифестов дерева), а функция ТОТАЛЬНА. Значит тип, заведённый применением манифеста в РАБОТАЮЩЕМ процессе, она вернёт точечным именем каталога — и это уедет в колонку, которая требует имени модели. Наблюдалось у зеркала: вставка ребра отвергалась проверкой схемы, то есть регистрация не проходила ВОВСЕ.

Пишешь в колонку словаря модели — спрашивай имя у ЖИВОЙ СТРОКИ каталога (`catalog_resource.object_type`; образец — `resource_mirror.modelTypeName`, который читает её в той же транзакции). Здесь остаются вызывающие, которым живая строка недоступна by construction и чей вход — только посеянные типы.

Свойство, различающее словари, объявлено выше и на нём же стоит сужение у писателей: имя каталога содержит точку ВСЕГДА, имя модели — НИКОГДА.

func ObjectType

func ObjectType(module, resource string) (string, bool)

ObjectType returns the rights-model object_type for (module, resource). ok=false when the pair is not in the closed table.

func PermissionsCoveringType

func PermissionsCoveringType(permissions []string, fgaType string) []string

PermissionsCoveringType keeps only the permissions that actually name the given FGA object type, resolved over the closed (module, resource) table.

Why the caller needs this

PermissionsToRelations answers "how strong is this role?" and deliberately ignores the module and resource segments — the tier comes from the verb. That is the right answer for the question it asks, and the wrong one for the question a tuple builder asks, which is "what may this role grant ON THIS OBJECT?". Applied to a hierarchy anchor the difference is not cosmetic: `vpc.network.*.*` is a verb-position wildcard, so the whole role reads as the admin tier, and `account:<A>#admin` accepts direct subjects and derives `project.super_admin: admin from account`, which every leaf type reads as `super_admin from project`. A role that only ever named vpc networks would hand over every resource in the account.

The rules path never had the gap — a rule carries (module, resource) and the anchor arm matches only when it covers the anchor's own type. This restores the same discipline on the legacy permissions-only path.

Matching

A permission is `module.resource.<group>.<verb>` (the seeded form, e.g. "vpc.network.*.*"); only the first two segments are read here. A `*` in either position broadens the pattern, and the closed table decides what it expands to — never a substring or prefix guess, so `iam.*` covers the account because "iam.account" is in the table, not because the strings look alike.

Returns nil when nothing covers the type. Callers must treat that as "grants nothing here" and NOT hand the empty slice to PermissionsToRelations, whose empty-input contract is the least-privilege viewer fallback — that fallback is for a role with no permissions at all, and reusing it here would turn "grants nothing on this anchor" into "grants read on this anchor".

func RoleVerbsFromSelectors

func RoleVerbsFromSelectors(selectors []domain.RuleSelector) []domain.RoleVerb

RoleVerbsFromSelectors — проекция «тип × глагол» из тех же селекторов, которыми роль материализуется.

ИСТОЧНИК ОДИН — селекторы: второй (разбор разрешений заново) дал бы два места об одном предмете, расходящиеся при первом же изменении формы правила, причём молча.

Тип в проекции остаётся ТОЧЕЧНЫМ — тем же, каким он назван в селекторах и каким его читает вердикт (`role_verb.object_type`). Набор глаголов при этом спрашивается у каталога по имени МОДЕЛИ: два словаря в одном соединении не совпадают никогда и молча, поэтому перевод делается здесь, ровно один раз.

Тип, которого каталог не знает, пар не даёт: материализатор на нём тоже не эмитит ничего, и выдумать за него набор значило бы дать право, которого движок не даёт.

func SplitObjectType

func SplitObjectType(typ string) (module, resource string, ok bool)

SplitObjectType splits a dotted "module.resource" key on the FIRST dot (the resource segment may itself contain no dot; module never does). ok=false when the input has no dot or an empty side. Single source of truth shared by the tuple builders in access_binding and access_binding/reconcile (previously duplicated in both — unified into one helper so the two paths cannot drift).

func TierOnlyVerbClasses

func TierOnlyVerbClasses() []string

TierOnlyVerbClasses — КОПИЯ набора классов, действующих ярусом.

Экспортирована ради гейта предпосылки: перечень, объявленный здесь, обязан быть подмножеством закрытого набора классов действия, и проверить это можно только назвав его. Второе объявление того же набора разошлось бы с первым молча.

func TypeHasVerbRelations

func TypeHasVerbRelations(fgaType string) bool

TypeHasVerbRelations reports whether the FGA object_type carries per-verb relations at all in the canonical authorization model — it says nothing about WHICH ones. The set is an attribute of the TYPE (VerbRelationsOfType), not a platform constant: `nlb_target_group` declares the canonical CRUD plus its two membership relations (NLB-TGT-1), so the previous wording — which named the five CRUD relations as THE set every verb-bearing type carries — described a tree that no longer exists and would have sent the next reader looking for a constant instead of the per-type table.

rbac-explicit-model-2026 P3 / D-6 (expand): the hierarchy ancestors `account` / `project` are now ALSO verb-bearing — the canonical fga_model.fga (P2) defines the full v_* set on both, so a grant of e.g. `iam.account.get` materializes `account:<id> # v_get @ subj` (object-level access to the account itself, NO cascade to its contents — D-2). This is purely ADDITIVE: account/project KEEP their tier relations (admin/editor/viewer, the write-authz anchors — D-7) and the scope_grant carrier still operates exactly as before. Only the v_* emission gate flips for these two types.

This is the single source of truth the FGA emitter consults before writing a per-verb `v_<verb>` tuple or a type-scoped `scope_grant` linking tuple: emitting either on a tier-only type writes a relation the model does not declare on that type. The external engine refused such a write outright, and the refusal travelled the whole way — permanent error, poisoned journal row, partial-grant desync. That refusal went away with the engine, which makes this closed set the thing that keeps emitter and model in step. The set is kept in lockstep with fga_model.fga; the CI drift-gate (authzmap/fga_model_drift_test.go) fails the build if this set ever diverges from the model.

func VerbRelationsOfType

func VerbRelationsOfType(fgaType string) []string

VerbRelationsOfType — имена `v_*`-отношений, которые канонический fga_model.fga определяет У ЭТОГО типа, в детерминированном (отсортированном) порядке; nil для неглагольного типа.

Это ЕДИНСТВЕННЫЙ источник набора для эмиссии: набор есть атрибут ТИПА, а не платформенная константа. Возвращается КОПИЯ — вызывающий не вправе испортить источник истины эмиссии.

func VerbsOfType

func VerbsOfType(fgaType string) []string

VerbsOfType — ГЛАГОЛЫ (без приставки `v_`), объявленные этим типом, отсортированно; nil для неглагольного типа.

Та же таблица, что и VerbRelationsOfType, но в форме, на которой говорит домен: домен оперирует глаголами правила, модель — именами отношений. Приведение живёт ЗДЕСЬ, у владельца таблицы, а не размножается по вызывающим.

Types

type CatalogEntry

type CatalogEntry struct {
	Module   string
	Resource string
}

CatalogEntry — one grantable (module, resource) pair from the closed objectTypes table. The dotted key "module.resource" is the canonical token form; Module / Resource are its two segments (split on the FIRST dot, same as SplitObjectType).

func Catalog

func Catalog() []CatalogEntry

Catalog returns every grantable (module, resource) pair in the closed objectTypes table, in a deterministic order (sorted by the dotted "module.resource" key). It is the SINGLE exported source of the grantable taxonomy — the PermissionCatalogService projects EXACTLY this set (no additions, no omissions), so a future objectTypes entry appears in the public catalog with no catalog-code change. Pairing it with ObjectType / TypeHasVerbRelations gives the per-type FGA object_type and verb-bearing flag.

type CatalogSeedRow

type CatalogSeedRow struct {
	Module   string
	Resource string
	// Dotted — производная форма, та же, какой говорит `role_verb.object_type` и
	// `role_rule_selectors.object_types`. Хранится колонкой под проверкой
	// согласия, а не собирается читателем: словарей имени типа в дереве ровно
	// два, и третьего здесь не заводится.
	Dotted string
}

CatalogSeedRow — одна строка посева каталога ресурсов.

func CatalogSeedResources

func CatalogSeedResources() []CatalogSeedRow

CatalogSeedResources — грантуемые пары в порядке точечного ключа.

type CatalogSeedVerb

type CatalogSeedVerb struct {
	Module   string
	Resource string
	// Verb — каноническая форма БЕЗ приставки отношения, та же, какой говорит
	// `role_verb.verb`. Приставку знает компилятор модели.
	Verb string
	// PerObject — строка ПРОИЗВОДИТ пообъектное отношение `v_<verb>` на своём
	// типе. Ложь означает глагол, законный АВТОРСКИ и не дающий кортежа ни на
	// одном объекте: его действие несёт ярус на родителе.
	//
	// Признак заведён задачей #1863, и предмет у него один: словарей ДВА.
	// `typeVerbRelations` отвечает на вопрос «какие отношения объявил тип», а
	// правило роли называет КЛАСС ДЕЙСТВИЯ — и это разные множества. Судить
	// второе первым значило отвергать `create` у всех, кроме реестра.
	PerObject bool
}

CatalogSeedVerb — одна строка посева каталога глаголов.

func CatalogSeedVerbs

func CatalogSeedVerbs() []CatalogSeedVerb

CatalogSeedVerbs — пары «(модуль, ресурс) × глагол» в детерминированном порядке, ОБЕИХ половин словаря: пообъектной и ярусной.

Тип, у которого набор глаголов не объявлен вовсе, строк не даёт НИ ОДНОЙ — ни пообъектных, ни ярусных: у него нет отношения `v_*`, правило на него не резолвится ничем, и ярусная строка обещала бы автору выдачу, которой не будет.

Порядок: сначала пообъектные глаголы ресурса (отсортированно), затем ярусные — так посев читается как «что тип объявил» плюс «что автор вправе назвать сверх этого», а не как один перемешанный список.

type ExpandAcceptance

type ExpandAcceptance int

ExpandAcceptance — исход входного контроля пары.

const (
	// ExpandAccepted — тип объявлен, отношение принадлежит поверхности и
	// объявлено ЭТИМ типом: план по паре собирается.
	ExpandAccepted ExpandAcceptance = iota
	// ExpandTypeNotDeclared — такого типа объектов модель не знает вовсе.
	// Виновное поле — тип.
	ExpandTypeNotDeclared
	// ExpandRelationOffSurface — отношение существует, но принадлежит внутренней
	// машинерии модели (переносчики охвата, подтягивающие резолверы,
	// платформенные отношения). Оно негодно при ЛЮБОМ типе, и его отказ старше
	// потипового вопроса — тон этого отказа часть контракта.
	ExpandRelationOffSurface
	// ExpandRelationNotOnType — отношение на поверхности, но ЭТОТ тип его не
	// объявляет. Ровно тот зазор, ради которого функция и заведена.
	ExpandRelationNotOnType
)

func AcceptExpand

func AcceptExpand(objectType, relation string) (ExpandAcceptance, error)

AcceptExpand судит пару (objectType, relation).

Ошибка означает «вшитая модель не разобралась» — это поломка сборки, а не негодный ввод, и вызывающий обязан ответить внутренней ошибкой, а не INVALID_ARGUMENT: иначе собственная поломка называлась бы виной вызывающего.

Порядок вопросов — от более общей координаты к более частной: тип · поверхность · объявление у типа. Он же порядок сообщений, и он же причина, по которой тон отказа для машинерии не изменился: отношение вне поверхности отвергается раньше, чем задан потиповый вопрос.

func (ExpandAcceptance) String

func (a ExpandAcceptance) String() string

type Relation

type Relation string

Relation — typed string for FGA relation names.

func PermissionsToRelations

func PermissionsToRelations(permissions []string) []Relation

PermissionsToRelations derives FGA relations from a role's permission list.

See package-level doc-comment for the strategy.

Output is deduplicated, never nil (always at least one relation — viewer fallback for the empty case).

Jump to

Keyboard shortcuts

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