Documentation
¶
Overview ¶
access_binding_scope.go — typed Scope enum for the RBAC v2 AccessBinding shape. The Scope tier anchors the binding in the cluster ▶ account ▶ project hierarchy; per-resourceName grants emit direct FGA tuples that respect Scope only as a sanity guard.
client_assertion.go — доменные понятия аутентификации клиента подписанным утверждением (задача #898, приёмка F2).
Что здесь и чего здесь нет ¶
Здесь — понятия, о которых обязаны договориться ВСЕ слои: чем клиент себя называет, из какого реестра он разрешается, и чем отличается повтор от первого предъявления. Разбор подписи, чтение базы и форма HTTP-ответа живут у своих слоёв: домен о них не знает (чистый Go, только stdlib).
Package domain — entities + value-types for kaname.
The domain layer depends on stdlib + multierr only — never on pgx, grpc-stubs, or sqlc. `kacho-proto` is permitted strictly for envelope types (Operation, status); for concrete IAM proto-structs we have `internal/dto/toproto`.
constants_extended.go — id-префиксы и константы. Стиль формата отличается от corelib `ids.NewID` (3-char prefix + 17-char crockford-base32) — здесь используется `<prefix>_<17-char crockford>` с `_` separator, чтобы префиксы могли быть длиннее 3 символов (`cag`, `org`, `cond`, `evt`, `soc`).
DB CHECK constraints в миграциях 0011..0014 enforce'ат соответствие формату per-таблица (например `^cag_[0-9a-hjkmnp-tv-z]{17}$`).
federated_assertion.go — доменные понятия перечня доверенных издателей утверждения (задача #1124).
Что этот перечень такое и чем он отличается от реестра клиентов ¶
Реестр клиентов отвечает на вопрос «наша ли это строка и её ли ключ». Перечень доверенных издателей отвечает на другой: «согласны ли МЫ, что вот этот ПОСТОРОННИЙ издатель вправе поручиться вот за этот свой субъект». Ключевой материал здесь принадлежит не нам, и решение о доверии — единственное, что у нас есть.
Отсюда две вещи, которые верны здесь и неверны у реестра клиентов:
- **запись доверия имеет собственный срок.** У клиента срок означает «эта строка больше не выдаёт»; здесь — «мы больше не ручаемся за постороннего». Снятие доверия сроком обязано работать, иначе выданное однажды доверие переживёт то, ради чего выдавалось;
- **пустой перечень означает «не доверяем никому».** Величина, которая может быть пустой и не проверена на непустоту, означает «принимаем любого» — класс, записанный в корпусе про круг законных отправителей. Тут он даёт не «приняли лишний заголовок», а токен платформы предъявителю, которого мы не заводили вовсе.
ids_extended.go — IAM-style id generator.
Формат: `<prefix>_<17-char crockford-base32>` (lowercase, без I/L/O/U). Отличается от corelib `ids.NewID` тем, что префикс может быть длиннее 3-х символов (DB CHECK constraints в миграциях 0011..0014).
Источник энтропии — crypto/rand. 17 символов crockford × 5 бит = 85 бит.
principal_claims.go — состав утверждений, называющий ПРИНЦИПАЛА, за которого говорит выпущенный нами токен.
Почему имена объявлены, а не выписываются по месту ¶
Читателей у этих имён больше одного: их СТАВИТ чеканка и их ЧИТАЕТ приёмная сторона. Пока имена живут литералами по своим пакетам, их различие не является ничьей находкой — оно не выражено и потому не может покраснеть. Разойтись же им есть чем, и разошлись бы они МОЛЧА: переименование на выпуске оставило бы читателя без принципала не отказом, а тишиной — токен проверился бы целиком, а за кого он говорит, читатель не узнал бы. Отличить такое состояние от «токен выдан системной личности» на стороне читателя нечем.
Что означает отсутствие каждого ¶
Вид и идентификатор ОБЯЗАТЕЛЬНЫ: «назвать некого» и «назван системный» — разные состояния, и смешивать их нельзя, потому что системная личность и есть та, которой принадлежат все служебные записи. Отображаемое имя косметическое: оно ни на одно решение не влияет и при отсутствии замещается идентификатором.
Index ¶
- Constants
- Variables
- func AccountScopeApplies(k LimitKind) bool
- func AllMaterializableTypes() []string
- func BindableScopes() []string
- func CustomDefinitionTierToScope(tierType, tierID string) (account, project string, ok bool)
- func DerivedIDSuffix(seed string) string
- func EncodeRules(rs Rules) ([]byte, error)
- func FGASubjectRef(subjectType, subjectID string) string
- func IsAssertionClientUnknown(err error) bool
- func IsAssertionReplayed(err error) bool
- func IsCountableKind(k LimitKind) bool
- func IsLabelSelectableType(objectType string) bool
- func IsPostureStatedKind(k LimitKind) bool
- func IsRetiredType(objectType string) bool
- func IsRoleAssignable(r Role, resourceType, resourceID string) bool
- func IsRoleAssignableInAccount(r Role, resourceType, resourceID, scopeOwningAccountID string) bool
- func IsScopeAnchorKind(bare string) bool
- func IsSeededResourceID(id string) bool
- func IsTrustedIssuerUnknown(err error) bool
- func IsVerbOfType(verb string, typeVerbs []string) bool
- func IsWellFormedModuleName(module string) bool
- func IsWellFormedObjectTypeName(objectType string) bool
- func ModuleNameGrammar() string
- func NewKac127ID(prefix string) string
- func NormalizeVerb(v string) string
- func ObjectTypeNameGrammar() string
- func OrderVerbsForDisplay(verbs []string) []string
- func ResolveVerbsAndTier(authored, typeVerbs []string) (verbs []string, tier string)
- func RetiredTypes() []string
- func RuleRefsByRule(rules Rules) [][]RoleRuleRef
- func ScopeTierByKind() map[string]Scope
- func ScopeTypeFromDotted(dotted string) (bare string, ok bool)
- func ScopeTypeToDotted(bare string) string
- func SeededResourceIDs() []string
- func ValidKeyIDForm(raw string) bool
- func ValidTargetType(dotted string) bool
- func ValidateAssertionID(raw string) error
- func ValidateRedirectURIs(field string, uris []string, required bool) error
- type AccessBinding
- type AccessBindingID
- type AccessBindingStatus
- type AccessTarget
- type Account
- type AccountID
- type AccountName
- type Arm
- type AssertionClient
- type AssertionClientKind
- type AssignableRole
- type AuditEventID
- type AuditOutboxEntry
- type AuditOutboxStatus
- type BasicCredential
- type Cluster
- type ClusterAdminEntry
- type ClusterAdminGrant
- type ClusterAdminGrantID
- type ClusterID
- type ClusterName
- type CountableKind
- type CredentialKind
- type Description
- type DisplayName
- type EffectiveLimit
- type Email
- type EventTypeName
- type ExternalSubject
- type FeedSource
- type GrantSubjectType
- type Group
- type GroupID
- type GroupMember
- type GroupName
- type InteractiveClient
- type InteractiveClientID
- type InteractiveClientName
- type InteractiveClientStatus
- type InviteStatus
- type KeyID
- type LabelKey
- type LabelVal
- type Labels
- type Limit
- type LimitCarrier
- type LimitFilter
- type LimitID
- type LimitKind
- type LimitScope
- type Membership
- type MembershipID
- type MembershipState
- type MembershipTuple
- type MirrorObject
- type ModuleSet
- type OAuthClientID
- type OAuthClientName
- type OperationID
- type Permission
- type Permissions
- type PrivilegeDerivation
- type Project
- type ProjectID
- type ProjectName
- type PrunedSelectorType
- type PublishedKey
- type RecoveryCompletion
- type ResourceRef
- type ResourceType
- type Role
- func (r Role) AuthoredVerbs(lookup TypeVerbLookup) []string
- func (r Role) CanonicalRank() int
- func (r Role) DefinitionTierID() string
- func (r Role) DefinitionTierType() string
- func (r Role) DisplayName() string
- func (r Role) EffectiveVerbs(lookup TypeVerbLookup) []string
- func (r Role) IsClusterAdminRole() bool
- func (r Role) IsSystemDerived() bool
- func (r Role) Purpose() string
- func (r Role) Validate(modules ModuleSet) error
- func (r Role) VerbNotes(lookup TypeVerbLookup) map[string]string
- func (r Role) WithoutComputedState() Role
- type RoleHealth
- type RoleID
- type RoleIntegrity
- type RoleLifecycle
- type RoleLifecycleState
- type RoleName
- type RoleRetirement
- type RoleRuleRef
- type RoleScopeGroup
- type RoleSegment
- type RoleVerb
- type Rule
- type RuleLabelSelector
- type RuleLifecycle
- type RulePolicy
- type RuleSelector
- type RuleState
- type Rules
- func (rs Rules) CoversType(dottedType string) bool
- func (rs Rules) HasAnchorRule() bool
- func (rs Rules) LabelSelectors() []RuleLabelSelector
- func (rs Rules) MaterializingSelectors() []RuleSelector
- func (rs Rules) MaterializingSelectorsInScope(scope Scope) []RuleSelector
- func (rs Rules) ScopeSelfVerbs(scopeResource string, typeVerbs []string) []string
- func (rs Rules) Validate(policy RulePolicy, modules ModuleSet) error
- type SAOAuthClientID
- type Scope
- type ScopeAnchor
- type SelectorPruneOutcome
- type ServiceAccount
- type ServiceAccountID
- type ServiceAccountOAuthClient
- type SessionRevocation
- type SigningAlgorithm
- type SigningKeyRecord
- type SigningKeyState
- type StructuralTuple
- type Subject
- type SubjectID
- type SubjectPrivilege
- type SubjectType
- type SubordinateResource
- type SvcAccountName
- type TargetMember
- type TrustedIssuer
- type TrustedSubject
- type TypeVerbLookup
- type User
- type UserID
- type UserOAuthClient
- type UserOAuthClientID
- type UserTokenRevocation
- type VerificationStatus
- type WithdrawnGrant
- type WithdrawnGrantCause
- type WithdrawnGrantSource
Constants ¶
const ( ScopeTypeClusterDotted = "iam.cluster" ScopeTypeAccountDotted = "iam.account" ScopeTypeProjectDotted = "iam.project" )
Dotted scope-type API projection (redesign-2026 F7). The AccessBinding scope-anchor is renamed resource_type/resource_id → scopeType/scopeId on the wire, with the word "resource" freed for the reintroduced target. The wire scopeType is dotted (`iam.{cluster,account,project}`) while the within-service storage keeps the bare kind (`cluster`/`account`/`project`) — the two are mapped at the API boundary (dto on output, handler on input). Only the three hierarchy tiers can anchor a binding, so the mapping is total over them.
const ( PrefixAccount = "acc" PrefixProject = "prj" PrefixUser = "usr" PrefixServiceAccount = "sva" PrefixGroup = "grp" PrefixRole = "rol" PrefixAccessBinding = "acb" // PrefixOperationIAM — separate prefix so api-gateway routes // `OperationService.Get(id)` correctly (by the first 3 characters of the // id). MUST differ from PrefixAccount — otherwise `acc<…>` (Account) and // an Operation on Account would collide. PrefixOperationIAM = "iop" )
ID prefixes for kaname resources. Mirrors the canonical prefix constants in `pkg/ids` so the use-case layer can refer to short names locally (`PrefixAccount`, `PrefixProject`, …) without re-importing corelib for trivial id construction.
const ( PrincipalTypeSystem = "system" PrincipalTypeAnonymous = "anonymous" PrincipalTypeUser = "user" PrincipalTypeServiceAccount = "service_account" PrincipalDisplayBootstrap = "kaname-bootstrap" PrincipalIDBootstrap = "bootstrap" )
PrincipalType — allowed values for kaname.operations.principal_type. 'system' / 'kaname-bootstrap' are used for internal/background flows; 'user' / 'service_account' come from OIDC at the api-gateway edge.
const ( PrefixClusterAdminGrant = "cag" PrefixCondition = "cond" PrefixSAOAuthClient = "soc" PrefixUserOAuthClient = "uoc" PrefixAuditEvent = "evt" // ClusterSingletonID — единственный валидный id для кластера. ClusterSingletonID = "cluster_root" // OwnerRoleID — deterministic id of the net-new `owner` system-role // (RBAC explicit-model 2026), seeded by migration 0035 as // `'rol' || substr(md5('owner'),1,17)`. The Account.Create auto-binding // references it. Kept here so the use-case does not re-hash the name at // runtime (and stays in lockstep with the migration seed id). OwnerRoleID = "rol72122ce96bfec66e2" // ClusterAdminRoleID — deterministic id of the system cluster-admin role // (`admin`, name 'admin'), seeded by migration 0001 as // `'rol' || substr(md5('admin'),1,17)` and re-seeded with its `*.*.*` rules by // migration 0031. This is the canonical "GLOBAL super-admin" role. // // IMPORTANT: the `owner` role (OwnerRoleID) carries the SAME `*.*.*` // wildcard SHAPE as cluster-admin, so the GLOBAL+all exception MUST be // keyed on this PINNED id (+ is_system), not on the shape alone — otherwise // owner would be misclassified as cluster-admin and slip past the reject. ClusterAdminRoleID = "rol21232f297a57a5a74" // SystemAdminRoleID — the hand-rolled deterministic id of the second `*.*.*` // superuser system role (`kacho-system.admin`), seeded by migration 0001 and // re-seeded with `*.*.*` rules by migration 0031. Also a legitimate // cluster-admin superuser. SystemAdminRoleID = "rol000000000sysadmin" // SystemViewerRoleID — рукописный детерминированный id второй системной роли // пола каталога (`kacho-system.viewer`), посеянной миграцией 0001. // // ДЛИНА ЕГО — 21, а не 20, и это ФАКТ ПРИМЕНЁННОЙ МИГРАЦИИ, а не описка, // которую можно поправить: id неизменяем на всю жизнь ресурса (ban #15, // «операции смены id НЕ существует»), а применённую миграцию править нельзя // (ban #5). Переименование поздней миграцией сверх того есть перенос выдачи с // одной роли на другую — тихое расширение прав, отвергаемое гейтом // `TestNoMigrationMovesGrantsBetweenRoles`. // // Поэтому литерал назван здесь и стоит в закрытом перечне // `SeededResourceIDs()`: проверка формы принимает то, что продукт сам посеял, // а не объявляет собственный посев негодным. Задача #1808. SystemViewerRoleID = "rol000000000sysviewer" )
id-префиксы. Singleton (`cluster_root`) — литерал, не генерируется. Outbox events (`evt_`) — ULID-based, длина 20..30.
const ( ConditionMFAFresh = "mfa_fresh" ConditionNonExpired = "non_expired" ConditionSourceIPInRange = "source_ip_in_range" ConditionBusinessHours = "business_hours" ConditionDeviceCompliant = "device_compliant" )
Condition expressions whitelist (migration 0012 access_binding_conditions_expression_whitelist_ck).
const ( // ClaimPrincipalType — вид принципала: `user` либо `service_account`. ClaimPrincipalType = "kaname_principal_type" // ClaimPrincipalID — идентификатор принципала. ClaimPrincipalID = "kaname_principal_id" // ClaimPrincipalDisplay — отображаемое имя принципала. Косметическое. ClaimPrincipalDisplay = "kaname_principal_display_name" )
const ( // MaxRules — rules[] cardinality per role. MaxRules = 64 // MaxCompiledPermissions — compiled-permissions cap. // Lockstep with DB CHECK + proto (size). MaxCompiledPermissions = 1024 )
const CustomRoleNameForm = `^[a-z][a-z0-9_]{0,40}$`
Regex / limits — centralised so domain.Validate and the DB CHECKs agree.
Формы имени ресурса здесь БОЛЬШЕ НЕТ: она объявлена один раз на всё дерево (`pkg/validate/nameform`), и iam читает то же объявление (#1279). Прежде здесь стояла своя — `^[a-z][-a-z0-9]{2,62}$`, — и расходилась с каноном в обе стороны: была УЖЕ него по началу имени (буква вместо буквы-или-цифры) и по длине (от 3 вместо 1), и ШИРЕ него по хвосту (допускала имя, кончающееся дефисом). Гейт единственности формы её не видел by construction: он ловит байт-идентичную копию канона, а независимо написанную регулярку — нет, и свою слепую зону называет сам.
Идентификатор роли остаётся своим и формой имени НЕ судится: `roles/vpc.admin` — то, на что ссылаются привязки, а не косметическая метка (записанное решение владельца, #715). CustomRoleNameForm — форма имени ПОЛЬЗОВАТЕЛЬСКОЙ роли. Зеркало ограничения таблицы `roles_custom_name_check` (`0056_role_definition_tier.sql`, применённая миграция, ban #5).
const EditorDeleteNote = "co-materialized on in-scope leaf objects, NOT on the account/project anchor itself"
EditorDeleteNote is the verbatim explanation of the editor delete-qualifier.
const EditorDeleteVerb = "delete*"
EditorDeleteVerb is the qualifier appended to an editor-tier role's effective verbs (delete of in-scope leaf objects, not the anchor).
const ErrCredentialKindField = "credential_kind"
ErrCredentialKindField — имя поля, которое обязан называть отказ. Объявлено одним местом, чтобы тон отказа не разошёлся между двумя глаголами выдачи. #nosec G101 -- это ИМЯ ПОЛЯ запроса, которое обязан назвать текст отказа (конвенция отказов Kachō), а не значение удостоверения.
const MaxAssertionIDLength = 256
MaxAssertionIDLength — потолок длины идентификатора однократности.
Значение выбирает ПРЕДЪЯВИТЕЛЬ, поэтому его длина — это его выбор нашего расхода. Потолок повторяет ограничение схемы: два места об одном предмете разошлись бы молча, поэтому проба сверяет их между собой.
const MaxSubjectsPerBinding = 32
MaxSubjectsPerBinding — hard upper bound on subjects[]. Anti-DoS + tractable per-subject audit/expand. 0 < n ≤ 32 enforced by NormalizeSubjects (sync, INVALID_ARGUMENT) and mirrored by the DB.
const MaxTargetResourcesPerBinding = 256
MaxTargetResourcesPerBinding — hard upper bound on target.resources[]. Anti-DoS + tractable per-object materialization: the reconciler intersects every rule-matched object with the target through the LINEAR AccessTarget.Contains, inside ONE synchronous create writer-tx holding the binding advisory lock, and re-runs it for every object created cluster-wide (forwardObjectForBinding). An unbounded set therefore costs |matched|×|target| comparisons on a hot path plus an unbounded JSONB row. 0 < n ≤ 256 enforced sync (INVALID_ARGUMENT), mirroring MaxSubjectsPerBinding.
const ShortIDLen = 20
ShortIDLen — full id length (prefix + body); matches pkg/ids.
const SystemRoleNameForm = `^[a-z][-a-z0-9]*(\.[a-z][a-z0-9_]*){0,2}$`
SystemRoleNameForm — форма имени СИСТЕМНОЙ роли. Зеркало ограничения таблицы `roles_system_name_check` того же файла: нижний регистр, дефис в первом сегменте, подчёркивание в сегментах после первого, не более трёх сегментов.
Здесь стояло `^roles/[a-z]+\.[a-z]+$`, и этой форме не удовлетворяла НИ ОДНА живая строка: предикат `grep -c "'roles/"` по каталогу миграций даёт ноль во всех файлах, а имена продукта записаны как `vpc.network.admin`. То есть `Role.Validate()` отвергал каждую системную роль платформы — и покраснеть это не могло, потому что системную роль в Go до задачи #1824 никто не строил.
Правило пережило свой предмет молча; сегодня оно зеркалит ограничение, которое действительно судит записываемую строку.
Variables ¶
var ErrAssertionClientUnknown = errors.New("client assertion: client does not resolve in a registry able to assert")
ErrAssertionClientUnknown — идентификатор не резолвится ни в одну строку реестра, СПОСОБНОГО к утверждению.
Один признак на два состояния — «строки нет вовсе» и «строка есть у вида клиента, не располагающего ключевым материалом», — и это не упрощение, а требование: различимые исходы дали бы предъявителю ОРАКУЛ существования. Он сообщал бы, заведён ли такой клиент, а по нему устанавливают и то, каким именно видом он заведён.
var ErrAssertionReplayed = errors.New("client assertion: single-use identifier already redeemed")
ErrAssertionReplayed — утверждение с этим идентификатором однократности уже предъявлялось ЭТИМ клиентом.
Отдельный признак, а не общий отказ: наружу все отказы аутентификации неразличимы (приёмка F2 §7), но ВНУТРЬ различимость обязана существовать — иначе счётчик исхода не с чем связать, и мёртвый контроль становится невидимым.
var ErrBasicCredentialRefused = errors.New("credential refused")
ErrBasicCredentialRefused — ЕДИНСТВЕННЫЙ отказ полосы базового секрета.
Неизвестный идентификатор, неверный секрет, истёкший срок, отозванное удостоверение, неактивный владелец, вид, не принимаемый этой поверхностью, — ОДНА И ТА ЖЕ ошибка. Различимый исход есть ОРАКУЛ: по нему отличают «нет такого» от «есть, но не ваш», то есть ровно то, что скрытие и должно закрыть.
Различимость живёт ВНУТРЬ — в счётчиках по причинам, не в значении ошибки. Отдельным исходом остаётся только НЕДОСТУПНОСТЬ АВТОРИТЕТА: это не отказ в удостоверении, а неспособность установить его состояние, и предлагать вызывающему переаутентифицироваться на неисправность, которую не исправит ни одно его удостоверение, значило бы вводить его в заблуждение.
var ErrEmpty = errors.New("required field is empty")
ErrEmpty — sentinel for empty required fields (used in NULL-validation before repo).
var ErrScopeMismatch = errors.New("scope does not match resource_type / resource_id")
ErrScopeMismatch — Scope does not match (resource_type, resource_id). Service-layer maps to gRPC InvalidArgument.
var ErrTrustedIssuerUnknown = errors.New(
"federated assertion: (issuer, subject) does not resolve in the trusted-issuer list")
ErrTrustedIssuerUnknown — пара (издатель, субъект) не резолвится ни в одну запись перечня.
ОДИН признак на все состояния «мы за это не ручаемся»: записи нет вовсе · доверие выдано другому субъекту того же издателя · доверие выдано, но строка, которую оно уполномочивало, снята. Различимые исходы дали бы предъявителю оракул: по ним устанавливают, заведено ли доверие и кому именно.
Functions ¶
func AccountScopeApplies ¶
AccountScopeApplies отвечает, применима ли область аккаунта к виду.
func AllMaterializableTypes ¶
func AllMaterializableTypes() []string
AllMaterializableTypes returns the closed, sorted, deduped set of `<module>.<resource>` types the reconciler can materialize per-object. It is the wildcard-expansion set for a BOUNDED-scope `*.*` rule (issue #224 / D-8a): an owner role bound at ACCOUNT/PROJECT becomes an explicit per-object admin on EVERY object kind inside the scope, instead of relying on the FGA derivation cascade.
This is a STRICT SUPERSET of labelSelectableTypes, differing by exactly registry.repositories (materializable, not label-selectable). Every other materializable type (mirror-fed vpc/compute/loadbalancer + EVERY iam-native type) is also ARM_LABELS-selectable. The iam content types (user/serviceAccount/group/ role/accessBinding) remain materializable by ARM_ANCHOR/ARM_NAMES (replacing the flat model's missing `from account` cascade with per-object materialization) AND are additionally label-selectable.
Sorted so the resulting selector + role_rule_selectors index are deterministic (stable fast-path JOIN + migration-seed lockstep, rule_wildcard_scope_test.go).
func BindableScopes ¶
func BindableScopes() []string
BindableScopes — копия закрытого набора областей выдачи, годных в предки. Копия, а не сам срез: набор закрыт, и вызывающий не вправе его пополнить.
func CustomDefinitionTierToScope ¶
CustomDefinitionTierToScope maps a wire definition_tier (dotted tierType + anchor id) to the account/project scope of a CUSTOM role (redesign-2026 F4). Pre-Phase-0 tierType is REQUIRED (prefix-derivation is B3-gated, so an empty tierType is rejected). iam.cluster is rejected — system roles are seeded by migration, never created via the public API. ok=false for an empty / iam.cluster / unknown tierType (the caller turns it into INVALID_ARGUMENT "Illegal argument definitionTier").
func DerivedIDSuffix ¶
DerivedIDSuffix возвращает первые семнадцать шестнадцатеричных символов `md5(seed)` — дословно то же, что постгресовое `substr(md5(seed), 1, 17)`.
Все шестнадцатеричные символы годны как крокфордова база-32 (`0-9a-f` ⊂ `[0-9a-hjkmnp-tv-z]`), поэтому производные идентификаторы проходят ограничения формы `soc_`/`cag_`.
Ничего секретного здесь нет: вход — посевные строки, выход — публичные идентификаторы. Дайджест зафиксирован уже применённой миграцией, поэтому иной дайджест был бы не «крепче», а просто перестал бы адресовать существующие строки.
func EncodeRules ¶
EncodeRules encodes domain Rules to the roles.rules JSONB payload shape.
func FGASubjectRef ¶
FGASubjectRef formats the FGA "user" side of a tuple for a subject:
user → user:<id> service_account → service_account:<id> group → group:<id>#member (computed relation expands group members)
Empty / unknown subject_type defaults to user (defensive). SINGLE source of truth shared by the AccessBinding grant-tuple builder (tuples.go / scope_grant_tuples.go) and the ARM_LABELS reconciler (reconcile) — the two MUST stay byte-symmetric, else a member's grant and revoke would target different FGA users and leak standing access.
func IsAssertionClientUnknown ¶
IsAssertionClientUnknown отвечает, не резолвится ли клиент.
func IsAssertionReplayed ¶
IsAssertionReplayed отвечает, отвергнуто ли предъявление как ПОВТОР.
Предикат, а не сравнение на месте: вызывающие обёртывают ошибку контекстом, и сравнение `err == ErrAssertionReplayed` перестало бы работать на первом же обёртывании — молча, потому что «не повтор» и «не смогли распознать» выглядят одинаково.
func IsCountableKind ¶
IsCountableKind reports membership in the closed catalogue.
func IsLabelSelectableType ¶
IsLabelSelectableType reports whether a `<module>.<resource>` type may carry a match_labels (ARM_LABELS) selector (feed-gate). Unified model: every iam-native type is label-selectable (project/account + the content types user/serviceAccount/group/role/accessBinding). It consults ONLY labelSelectableTypes — registry.repositories is materializable but NOT label-selectable, so it stays out of this predicate.
func IsPostureStatedKind ¶
IsPostureStatedKind — величину этого вида объявляет посадка, а не авторитет.
func IsRetiredType ¶
IsRetiredType reports whether a dotted `<module>.<resource>` names a resource the platform has retired. A retired type is not grantable on any rule arm.
func IsRoleAssignable ¶
IsRoleAssignable reports whether role r may be bound on resource (resourceType, resourceID) per the STRICT assignability matrix.
func IsRoleAssignableInAccount ¶
IsRoleAssignableInAccount extends IsRoleAssignable with the hierarchy-down rule that needs the scope's RESOLVED owning-account — knowledge the stateless predicate does not have (acceptance IAM-1-25). scopeOwningAccountID is the account that OWNS the scope anchor: for an account scope it is the account id itself; for a project scope it is the project's account_id (resolved by the caller via a project→account lookup); for cluster / cross-service scopes it is "".
It admits the strict matrix (IsRoleAssignable) PLUS the single hierarchy-down case: an iam.account-tier custom role is assignable on a PROJECT nested in the role's own account (role.account_id == the project's owning account). The account boundary is never crossed — an account-role of a DIFFERENT account stays not-assignable — and no other tier gains breadth (system stays everywhere, project-role stays own-project). scopeOwningAccountID=="" (unresolved / non-account scope) collapses to the strict predicate, so a missing resolve never over-grants (fail-closed).
func IsScopeAnchorKind ¶
IsScopeAnchorKind reports whether a bare within-service kind may anchor an AccessBinding. Only the three hierarchy tiers can; a per-object type names an object UNDER the anchor and belongs to the `target` axis (F8), whose vocabulary is the materialization feed (`ValidTargetType`).
func IsSeededResourceID ¶
IsSeededResourceID отвечает, назван ли литерал закрытым перечнем посеянного.
Проверку ПРЕФИКСА он на себя НЕ берёт: её делает вызывающий (`shared.ValidateResourceID`), поэтому посеянный id роли не проходит там, где ждут аккаунт.
func IsTrustedIssuerUnknown ¶
IsTrustedIssuerUnknown отвечает, не резолвится ли пара.
Предикат, а не сравнение на месте: вызывающие обёртывают ошибку контекстом, и сравнение на равенство перестало бы работать на первом же обёртывании — молча, потому что «не доверенный» и «не смогли распознать» выглядят одинаково, а второе означало бы недоступность реестра, у которой исход СВОЙ.
func IsVerbOfType ¶
IsVerbOfType сообщает, объявляет ли ТИП этот глагол, то есть материализуется ли он как отношение `v_<глагол>` НА ЭТОМ типе.
Заменила снятый глобальный словарь глаголов на путях эмиссии. Разница не косметическая: глобальный словарь отвечал одинаково для всех типов, поэтому «у этого типа такого глагола нет» было невыразимо — и правило, называющее соседний глагол, порождало кортеж с отношением, которого у типа не существует. Пустой набор не принадлежит никому (fail-closed): тип, не объявивший ничего, не получает ни одного `v_*`.
func IsWellFormedModuleName ¶
IsWellFormedModuleName — годно ли имя по ФОРМЕ, безотносительно членства.
Читателей у грамматики ДВА, и они судят разные поля: сегмент `module` правила роли (validateModule) и оболочку манифеста (`module:`). Вторая копия образца разошлась бы с первой молча — на том имени, о котором знает только одна.
Подстановочный знак `*` формой имени НЕ является: он маркер политики, и разбирается Rule.Validate отдельно.
func IsWellFormedObjectTypeName ¶
IsWellFormedObjectTypeName — годно ли имя типа объекта по ФОРМЕ, безотносительно того, объявлял ли его кто-нибудь.
func ModuleNameGrammar ¶
func ModuleNameGrammar() string
ModuleNameGrammar — образец имени модуля СТРОКОЙ, для текстов отказа.
Отказ обязан назвать не только негодный токен, но и правило: без правила автор манифеста узнаёт, что ошибся, и не узнаёт, чем именно. Величина берётся у того же объявления, что судит, — выписанная рядом, она разошлась бы с ним молча.
func NewKac127ID ¶
NewKac127ID возвращает идентификатор формата `<prefix>_<17-char crockford>`. Panic если prefix пустой (programmer error: префикс приходит из package-level константы).
func NormalizeVerb ¶
NormalizeVerb — ЕДИНСТВЕННАЯ точка приведения имени глагола к канонической форме.
Разрыв, который она закрывает, был двусторонним. Проверка принадлежности приводила ВХОД, а индекс словаря строился ДОСЛОВНО — словарная запись с заглавной буквой не нашлась бы никогда. И наоборот: имя отношения собиралось из АВТОРСКОГО написания, поэтому написание, отличающееся регистром, проходило проверку и адресовало отношение, которого в модели нет; владелец модели отвергает такую запись окончательно, а отказ считается постоянным — строка навсегда блокирует свою партицию очереди.
Приведение ТОЖДЕСТВЕННО на всех существующих глаголах, поэтому ни одно отношение, ни один кортеж и ни одна запись каталога не меняются.
func ObjectTypeNameGrammar ¶
func ObjectTypeNameGrammar() string
ObjectTypeNameGrammar — образец имени типа СТРОКОЙ, для текстов отказа и для сверки с ограничением базы.
Отказ обязан назвать не только негодный токен, но и правило: без правила автор манифеста узнаёт, что ошибся, и не узнаёт, чем именно. Величина берётся у того же объявления, что судит, — выписанная рядом, она разошлась бы с ним молча.
func OrderVerbsForDisplay ¶
OrderVerbsForDisplay возвращает глаголы в КАНОНИЧЕСКОМ порядке показа: сперва старшинство (get/list/create/update/delete), затем всё остальное — стабильно, по алфавиту.
Точка порядка ОДНА на всё превью и на публичное поле каталога. Порядок этих поверхностей — часть контракта: его читают существующие клиенты. Пока он жил внутри превью, поле каталога брало порядок из своего источника — и смена источника на пересечение наборов молча переставила бы значения по алфавиту.
func ResolveVerbsAndTier ¶
ResolveVerbsAndTier expands a rule's authored verbs (verb `*` → набор ТИПА) and derives the per-RULE back-compat tier (strongest verb-class among the rule's verbs), mapped the SAME way the consumer authz-gate resolves an action: get/list → viewer ; create/update (+ domain mutations) → editor ; delete → admin. Per-RULE, never whole-role (B-11). The tier tuple keeps tier-based Check call-sites working; the v_* tuples carry the precise per-verb enforcement. typeVerbs — набор глаголов, объявленный ТИПОМ, на который правило адресовано. Он приходит ПАРАМЕТРОМ от вызывающего, который тип уже знает: владельцем таблицы остаётся authzmap, а домен — без внешних зависимостей (см. объявление файла). Вызывающий, у которого типа нет (правило не резолвится ни в один известный), передаёт словарь, общий для всех ресурсов, — это его решение, не домена.
func RetiredTypes ¶
func RetiredTypes() []string
RetiredTypes returns the closed, sorted set of retired dotted types. Exported so the gates that keep the retirement complete can be driven from ONE list rather than from a copy of it.
func RuleRefsByRule ¶
func RuleRefsByRule(rules Rules) [][]RoleRuleRef
RuleRefsByRule — объявленные сегменты, разложенные ПО ПРАВИЛАМ.
Длина результата равна длине входа ВСЕГДА: правило без адресуемых сегментов даёт пустой срез, а не пропускается. Иначе индекс перестал бы указывать на своё правило — то есть ключ RuleState.RuleIndex стал бы ложным.
Чем это отличается от [RuleRefsOf], и различие несущее ¶
RuleRefsOf дедуплицирует сегменты ПО ВСЕЙ РОЛИ: два правила, объявившие один сегмент, дают одну строку — и это верно для вопроса «сколько адресуемых сегментов у роли». Здесь наоборот: дедупликация только ВНУТРИ правила, потому что сегмент, потерянный обоими правилами, потерян у ОБОИХ, и схлопнув их, мы объявили бы одно из них действующим.
func ScopeTierByKind ¶
ScopeTierByKind — копия словаря «вид → ярус».
Экспорт заведён ради ОДНОГО вызывающего — пробы согласия с триггером схемы (см. §«Третье место» выше). Второе объявление отношения живёт в другом пакете, поэтому сверить его иначе нечем: без этого читателя согласие двух объявлений было бы обещанием, а не проверкой.
Отдаётся копия: словарь неизменяем by construction, и вызывающий не должен иметь возможности это нарушить.
func ScopeTypeFromDotted ¶
ScopeTypeFromDotted maps the dotted wire scopeType to the bare within-service anchor kind. ok=false for any value outside the closed three-tier set (empty, non-dotted bare, or unknown dotted) — the caller rejects it with InvalidArgument.
func ScopeTypeToDotted ¶
ScopeTypeToDotted maps the bare within-service anchor kind to the dotted wire scopeType. An unrecognized kind is returned unchanged (defensive — a binding anchor is always one of the three tiers).
func SeededResourceIDs ¶
func SeededResourceIDs() []string
SeededResourceIDs возвращает перечень для гейта предмета. Копия — чтобы вызывающий не мог расширить приём, правя чужую карту.
func ValidKeyIDForm ¶
ValidKeyIDForm — форма идентификатора ключа как предикат для приёмной стороны. Отдельная функция, а не экспортированная регулярка: регулярку вызывающий скопировал бы, и две копии разошлись бы молча.
func ValidTargetType ¶
ValidTargetType reports whether a dotted `<module>.<resource>` may be named as a per-object AccessBinding target. The answer is membership in the materialization feed (`AllMaterializableTypes`), because that is the only place a target type is ever used: the reconciler calls AccessTarget.Contains with the object type taken from the feed and keeps the object on an exact string match. The wildcard `*`, malformed input and anything the feed does not emit are all refused.
It is a lookup, not a derivation. The predicate used to re-spell the dotted form into the bare scope-anchor vocabulary (`compute.instance` → `compute_instance`, checked against a hand-written whitelist that has since been removed — the anchor vocabulary is the three hierarchy tiers and is declared once, `scopeAnchorTiers`) and claimed that kept the two in sync by construction; it did not, because the two vocabularies answer different questions and are spelled in different conventions. Deriving one name from another instead of resolving it against the one table that owns it is the same mistake as reading a region out of a zone's name (data-integrity.md) — it is silently wrong for every pair the two conventions disagree on, in BOTH directions: a type the feed emits is refused, so the grant can only be written as the whole anchor and the check widens what it appears to restrict; and a type the feed never emits is accepted, stored and reconciled, matching nothing and telling the caller nothing.
func ValidateAssertionID ¶
ValidateAssertionID проверяет форму идентификатора однократности ДО того, как он попадёт в хранилище.
Отказ здесь наступает раньше обращения к базе — иначе потолок держало бы ограничение схемы, а вызывающий получал бы отказ хранилища вместо отказа формы, и счётчик исхода двигался бы не тот.
func ValidateRedirectURIs ¶
ValidateRedirectURIs — the redirect rule, exported because both the Create and the Update path must apply exactly the same one (api-conventions: a mutable field is validated on Update by the same rules as on Create).
WHY https IS NOT COSMETIC. A redirect target is where the authorization code — a credential — is delivered. A plaintext target hands it to anyone on the path; a relative or opaque one lets the provider's own matching rules decide where it lands. Both are rejected here rather than left to the provider, because the provider's answer arrives too late to be a contract: the caller would see a peer error instead of a named field.
Types ¶
type AccessBinding ¶
type AccessBinding struct {
ID AccessBindingID
SubjectType SubjectType
SubjectID SubjectID
RoleID RoleID
ResourceType ResourceType
ResourceID string // opaque id (any prefix, cross-service OK)
Scope Scope // RBAC v2 — anchor tier (CLUSTER/ACCOUNT/PROJECT)
Status AccessBindingStatus // PENDING|ACTIVE|REVOKED
ExpiresAt *time.Time // nullable — TTL
GrantedByUserID UserID // audit
RevokedAt *time.Time // nullable
RevokedByUserID *UserID // nullable
CreatedAt time.Time
// MaterializedAt — OUTPUT-ONLY. When this binding's per-object access last
// became live: MAX(updated_at) over its ACTIVE rows in
// access_binding_target_members (the reconciler's ledger). Zero when nothing
// has materialized yet.
//
// It exists because Kachō is eventually-consistent by contract: Operation.done
// means the binding row is DURABLE, never that the FGA tuples are visible
// (gating done on downstream visibility is forbidden — ban #9). In that window
// the grantee's 403 is byte-identical to a real denial, leaving the granting
// admin with nothing to distinguish "propagating" from "wrong grant" and
// nothing to poll. This field is the observable — NOT a barrier: it is filled
// by the read path from an already-written ledger and never blocks a write.
//
// Legitimately zero forever for a binding that materializes no per-object
// membership (CLUSTER scope — served by the flat short-circuit; a legacy
// permissions-only role with no authored rules[]). Zero therefore means "no
// ACTIVE per-object member", NOT "the grant is broken".
//
// Not persisted on the access_bindings row; filled by the read-side batch
// projection, like Subjects.
MaterializedAt time.Time
// DeletionProtection guards the binding from Delete (RBAC explicit-model 2026
// P6 — D-10, by the image of vpc.address.deletion_protection). The owner
// auto-binding created on Account.Create sets it true; Delete on a protected
// binding → FAILED_PRECONDITION (sync pre-check + atomic CAS backstop). Cleared
// via Update(update_mask=["deletion_protection"]) (C-03). Default false.
DeletionProtection bool
// GrantedRelation — ВТОРАЯ ФОРМА выдачи: имя отношения модели, выдаваемое
// напрямую на области выдачи, взаимоисключающе с RoleID (ограничение БД
// `access_bindings_grant_form_ck`). Роль раздаёт ГЛАГОЛЫ через свои правила, а
// встроенные права платформы выражены именованными отношениями
// (`system_viewer`, `quota_reader`, `viewer` на кластере) — подобрать роль под
// каждое означало бы завести роли с пустыми правилами.
//
// Непусто ТОЛЬКО у системной выдачи: отношение — внутреннее имя, от которого
// зависит решение о доступе, и выдавать его вправе только платформа. На вход
// создания это поле не принимается вовсе — его нет в запросе, поэтому «принято
// и проигнорировано» здесь невозможно by construction.
GrantedRelation string
// System — выдача заведена платформой (встроенный доступ: права служебных
// учёток, публичное чтение справочников). OUTPUT-ONLY на публичном контракте.
//
// Отвечает на вопрос «кто выдал» точнее, чем подставленная учётка: выдал не
// человек, а платформа, поэтому GrantedByUserID у такой выдачи пуст, и это не
// пробел аудита, а его содержание.
System bool
// Subjects — the full multi-subject set (RBAC rules-model 2026).
// Persisted in the access_binding_subjects child table; SubjectType/
// SubjectID above remain the legacy single = Subjects[0] (projection +
// the active-grant UNIQUE anchor). Empty on a row read without the child
// load — read-side Get/List fills it. On Create the use-case
// NormalizeSubjects-resolves it from the request before persisting.
Subjects []Subject
// Labels — tenant-facing метки САМОГО ресурса AccessBinding. Делают
// AccessBinding label-selectable наравне с account/project (ARM_LABELS-грант
// на iam.accessBinding → v_list по `labels @> matchLabels`; List фильтрует
// viewer ∪ v_list).
Labels Labels
// Target — WHICH objects under the scope-anchor the grant applies to
// (redesign-2026 F8). AllInScope = whole anchor (incl. future); Resources =
// closed per-object set. The zero value is treated as AllInScope (legacy /
// internal rows); the public Create RPC rejects a missing target (least-priv).
Target AccessTarget
}
AccessBinding — link (subject_type, subject_id) ↔ role_id ↔ (resource_type, resource_id) with lifecycle fields (status, expires_at, granted_by, revoked_at/revoked_by).
State machine: PENDING → ACTIVE → REVOKED (terminal). Transitions are atomic CAS-style UPDATEs (no TOCTOU). REVOKED is irreversible via `WHERE status IN ('PENDING','ACTIVE')`.
DB partial UNIQUE access_bindings_active_grant_uniq (migration 0003; WHERE revoked_at IS NULL) → strict INSERT: a duplicate active grant raises ErrAlreadyExists with verbatim text «these permissions are already granted to <subject_id> on <res_type>:<res_id>». Re-grant after revoke is allowed (revoked rows are out of the partial UNIQUE scope).
func (AccessBinding) StructuralParent ¶
func (b AccessBinding) StructuralParent() (StructuralTuple, bool)
StructuralParent returns the binding's scope parent-pointer:
project:<resourceID> → project → iam_access_binding:<id> account:<resourceID> → account → iam_access_binding:<id> cluster:<resourceID> → cluster → iam_access_binding:<id>
This triple is what `iam_access_binding.super_admin: super_admin from project or admin from account or any_admin from cluster` resolves over — the sole enabler of the cascade on a binding. ok=false when the scope is not a hierarchy parent, the binding has no id, or the scope has no id.
The empty-scope-id case is unreachable through the API — validateScopeID pins the cluster tier to the singleton id and requires a non-empty account/project id — and the guard is here for what it prevents rather than for what it expects: without it such a row projects `cluster:` with an empty object id, which the store rejects, and a rejected write in the outbox is retried rather than dropped, so one malformed row would hold up its partition indefinitely.
It is deliberately INDEPENDENT of status. A revoked binding keeps its row, and it is still a record that has to be readable and deletable by the administrator of its account; a status filter here would take that away precisely when the queue is behind. It cannot bring the grant back: what the grantee held were tuples on the SCOPE object, and this is a pointer at the binding, not a grant on the scope. Locked by service/cascade_queue_independence_integration_test.go (TestRevokedBindingStaysManageableAndGrantsNothing).
func (AccessBinding) Validate ¶
func (b AccessBinding) Validate() error
type AccessBindingID ¶
type AccessBindingID string
type AccessBindingStatus ¶
type AccessBindingStatus string
AccessBindingStatus — enum lifecycle (migration 0011 CHECK).
const ( AccessBindingStatusPending AccessBindingStatus = "PENDING" AccessBindingStatusActive AccessBindingStatus = "ACTIVE" AccessBindingStatusRevoked AccessBindingStatus = "REVOKED" )
func (AccessBindingStatus) Validate ¶
func (s AccessBindingStatus) Validate() error
type AccessTarget ¶
type AccessTarget struct {
AllInScope bool // whole-anchor grant (incl. future objects)
Resources []ResourceRef // per-object grant (mutually exclusive with AllInScope)
}
AccessTarget is the object-selection of an AccessBinding (F8). Exactly one arm is meaningful; the zero value is the whole-anchor grant (AllInScope semantics).
func (AccessTarget) Contains ¶
func (t AccessTarget) Contains(dottedType, id string) bool
Contains reports whether the closed per-object target set lists the object (dottedType, id). It is the least-privilege membership test the reconciler applies so a role.rules match materializes ONLY an object the target also lists. An AllInScope/empty target lists NO explicit object here (its breadth is the whole anchor, materialized by the reconciler's all-in-scope path — not this test), so Contains returns false for it; callers gate on IsEmpty()/AllInScope before using it.
func (AccessTarget) Digest ¶
func (t AccessTarget) Digest() string
Digest returns a deterministic, set-based canonicalization of the target for the active-grant partial UNIQUE. AllInScope / empty → "all"; a resource set → a hash of its SORTED "type:id" members (order-independent — the same set in any order collides, IAM-1-29).
func (AccessTarget) IsEmpty ¶
func (t AccessTarget) IsEmpty() bool
IsEmpty reports whether neither arm is set (no explicit selection). An empty target is treated as AllInScope for storage/digest, but the public Create RPC rejects it (target REQUIRED).
func (AccessTarget) ResourceIDsForTypes ¶
func (t AccessTarget) ResourceIDsForTypes(types []string) []string
ResourceIDsForTypes returns the ids of the per-object target resources whose dotted type is in `types` (order-preserving, de-duplicated). It lets the reconciler resolve a per-object target's ARM_ANCHOR candidates by id (MatchByIDs) instead of scanning the whole scope (MatchAllInScope) — the least-privilege materialization path (IAM-1-21). Empty when no listed resource matches the given types.
func (AccessTarget) Validate ¶
func (t AccessTarget) Validate() error
Validate checks the target well-formedness: the two arms are mutually exclusive, and every per-object ResourceRef carries a non-empty id and a type drawn from the closed dotted type-registry. AllInScope / empty is always well-formed.
type Account ¶
type Account struct {
ID AccountID
Name AccountName
Description Description
Labels Labels
OwnerUserID UserID
CreatedAt time.Time
}
Account — top-level tenant («организация» как товар продукта IAM). Замещает прежнюю связку tenant+folder из retired kacho-resource-manager. Уникальное имя глобально (DB UNIQUE accounts_name_unique).
FK: owner_user_id → users(id) ON DELETE RESTRICT. Удаление RESTRICT при наличии Project / ServiceAccount / Group / custom Role.
func (Account) StructuralFacts ¶
func (a Account) StructuralFacts() []StructuralTuple
StructuralFacts returns the two structural facts the model reads on `account`:
cluster:<singleton> → cluster → account:<id> (levels 1-2 reach it here) user:<owner> → owner → account:<id>
The owner is a structural fact and not merely a materialized grant: an account is created by self-service, so tearing it down has to be as reliable as creating it, which is why `account`'s five verbs read `or owner` directly.
type AccountID ¶
type AccountID string
AccountID / ProjectID / ... — newtypes over string, never bare ids.
type AccountName ¶
type AccountName string
Names — every newtype has its own regex.
func (AccountName) Validate ¶
func (n AccountName) Validate() error
type AssertionClient ¶
type AssertionClient struct {
// ID — НАШ идентификатор строки реестра. Им и только им клиент себя
// называет: он же издатель и субъект утверждения.
ID string
// Kind — из какой таблицы реестра строка прочитана.
Kind AssertionClientKind
// OwnerID — принципал, от чьего имени клиент получает токен.
OwnerID string
// PublicKeyPEM — зарегистрированный открытый ключ.
PublicKeyPEM string
// Algorithm — ЗАРЕГИСТРИРОВАННЫЙ алгоритм. Пустое значение — законный вход
// схемы, и означает оно «ключа нет», а НЕ «любой алгоритм».
Algorithm string
// ExpiresAt — момент истечения клиента в Unix-секундах; 0 означает
// «бессрочно», и это законное состояние схемы (колонка допускает NULL).
ExpiresAt int64
// Отзыв ключа НЕ является здесь полем, и это не упущение: колонки отзыва в
// схеме обеих таблиц НЕТ — отзыв выражен СНЯТИЕМ СТРОКИ. Завести поле
// значило бы описать состояние, которого схема не допускает, и построить
// на нём пробу, которую нечем поставить (F1 §14 п. 2). Отозванный клиент
// не резолвится вовсе, и отказ наступает на том же шаге, что для
// несуществующего, — то есть требование «отозванный токена не получает»
// выполняется by construction, а не проверкой поля.
// OwnerActive — владелец в состоянии ACTIVE. Не-ACTIVE — это ДВА значения
// словаря (PENDING и BLOCKED), и оба обязаны давать один исход.
OwnerActive bool
// DeclaredAudiences — сужение адресатов, ОБЪЯВЛЕННОЕ заказчиком ключа при
// его выдаче (`IssueSAKeyRequest.audience`, задача #1136).
//
// ПУСТОЙ ПЕРЕЧЕНЬ ОЗНАЧАЕТ «СУЖЕНИЯ НЕ ОБЪЯВЛЕНО», А НЕ «ЛЮБОЙ АДРЕСАТ», и
// это НЕ тот класс, что пустой список доверенных отправителей. Там пустое
// значение снимало единственный контроль; здесь оно оставляет ВНЕШНЮЮ
// границу — объявленный посадкой перечень адресатов, который страж старта
// требует непустым и без которого выдача не строится вовсе. Сужение ключа
// действует ВНУТРИ этой границы и никогда её не расширяет: адресат, которого
// посадка не объявила, не выдаётся ни одному ключу, как бы он ни объявился.
//
// У клиента пользовательского токена перечня нет BY CONSTRUCTION: адресата
// его токена проставляет сам сервис из своей настройки, и заказчик не вправе
// назвать его вовсе (решение Р2, `interactive_client.proto`). Поэтому сужать
// там нечем, и пустое значение на том пути — не умолчание, а отсутствие
// предмета.
DeclaredAudiences []string
}
AssertionClient — строка реестра, способная предъявить утверждение.
Зеркального значения (идентификатора клиента во внешнем сервере) здесь НЕТ, и это не упущение: оно не участвует в разрешении клиента ни как второй ключ поиска, ни как запасной (приёмка F2 §2.1). Субъект с двумя именами дал бы две записи журнала об одном действии и два ключа однократности на одно утверждение — то есть отменил бы однократность, сохранив её форму.
func (AssertionClient) CanPresentAssertion ¶
func (c AssertionClient) CanPresentAssertion() bool
CanPresentAssertion отвечает, способна ли строка вообще участвовать в аутентификации утверждением.
Пустой зарегистрированный алгоритм и пустой ключ — «ключа нет». Это тот же класс, что пустой список доверенных отправителей: значение, которое может быть пустым и не проверено на непустоту, означает «не сужаем».
type AssertionClientKind ¶
type AssertionClientKind string
AssertionClientKind — вид клиента, способного доказать владение ключом.
Словарь ЗАКРЫТ и содержит ровно те виды, у которых в схеме есть ключевой материал. Интерактивного клиента здесь нет НАМЕРЕННО: у него нет способа доказать владение ключом by construction, поэтому он не резолвится ни во что, и отказ наступает на том же шаге, что и для несуществующего клиента (приёмка F2 §2.9). Формулировка «нашли строку, ключ пуст» потребовала бы, чтобы кто-то поддерживал в третьей таблице инвариант «ключа быть не должно»; инвариант, который надо поддерживать, ломается, а выраженный отсутствием таблицы на пути — нет.
const ( // AssertionClientUser — клиент, через который выдаётся токен пользователя. AssertionClientUser AssertionClientKind = "user" // AssertionClientServiceAccount — клиент ключа служебной учётки. AssertionClientServiceAccount AssertionClientKind = "service_account" )
func AssertionClientKinds ¶
func AssertionClientKinds() []AssertionClientKind
AssertionClientKinds возвращает закрытый словарь целиком.
type AssignableRole ¶
type AssignableRole struct {
RoleID RoleID
Name RoleName
Description Description
IsSystem bool
ScopeGroup RoleScopeGroup
CreatedAt time.Time
}
AssignableRole — the lean projection of a Role for the grant-form picker Carries only publicly-safe fields the UI needs to render + group the picker (id / resolved name / description / is_system / server-computed scope_group / created_at) — NO permissions array and no infra-sensitive fields. ScopeGroup is computed via ScopeGroupOf.
type AuditEventID ¶
type AuditEventID string
AuditEventID — идентификатор записи журнала: `evt_` плюс 20…30 символов crockford-base32 (ограничение `audit_outbox_id_check`, миграция `0001_initial.sql`).
Тело СЛУЧАЙНО, а не производно от времени: производитель (`pg.newAuditEventID`) берёт 14 байт у источника случайности и печатает из них 22 символа. Порядок по идентификатору поэтому НЕ является порядком по времени; сортировать журнал надо по `created_at`. Прежняя редакция называла его ULID — то есть обещала ровно ту сортируемость, которой нет.
func (AuditEventID) Validate ¶
func (id AuditEventID) Validate() error
type AuditOutboxEntry ¶
type AuditOutboxEntry struct {
ID AuditEventID
EventType EventTypeName
TenantAccountID *AccountID
EventPayload json.RawMessage
Status AuditOutboxStatus
Attempts int
CreatedAt time.Time
NextAttemptAt time.Time
}
AuditOutboxEntry — строка журнала аудита `kaname.audit_outbox` (заведён миграцией `0001_initial.sql`), дописываемая только вперёд.
Что происходит на самом деле ¶
Строка ложится в ТУ ЖЕ транзакцию, что и мутация домена, и оттуда вывозится в приёмник журнала — поток структурных записей службы (`services/iam/cmd/kaname/audit_shipper_wiring.go`, механизм — `pkg/audit`). Состояние строки после этого помечено доставленным, а состояние очереди целиком снимает периодический сканер (`services/iam/cmd/kaname/outbox_metrics_wiring.go`).
Здесь было описано ОТСУТСТВИЕ доставки — у него больше нет предмета ¶
Прежняя редакция объясняла, почему дренаж неконструируем: приёмника аудита не существовало ни одного, и из четырёх объявленных состояний достижимо было ровно одно. Оба утверждения были верны на день записи и перестали быть верными вместе с приёмником (#812): состояний теперь ДВА и оба достижимы, потому что словарь сужен до того, что продукт производит.
Здесь стояло описание, неверное ЧЕТЫРЕЖДЫ ¶
Прежняя редакция объявляла: дренаж отправляет строки в топик брокера; журнал заведён миграцией 0013; идентификатор — ULID; ULID сортируется по времени. Верно из этого ноль. Брокера в продукте нет и он запрещён non-negotiable #7; миграция 0013 — про снятие перечня условий обхода; идентификатор собирается из СЛУЧАЙНЫХ байт (`newAuditEventID`, 22 символа crockford-base32), то есть по времени не сортируется ни в каком порядке. Опасен был не сам текст, а его направление: он описывал систему СЛОЖНЕЕ и исправнее, чем она есть, поэтому читатель уходил искать дренаж и топик вместо того, чтобы увидеть, что доставки нет вовсе.
func (AuditOutboxEntry) Validate ¶
func (e AuditOutboxEntry) Validate() error
type AuditOutboxStatus ¶
type AuditOutboxStatus string
AuditOutboxStatus — состояние ДОСТАВКИ строки журнала.
Значений ровно два, и столько же допускает ограничение таблицы (миграция `20260823001500_audit_journal_gets_its_receiver.sql`). Прежде их объявлялось четыре: «в полёте» и «отказ» не писал никто и никогда — полёта не существует, потому что строка держится блокировкой своей транзакции от клейма до пометки, а терминального отказа не существует, потому что у приёмника нет класса «не приму никогда». Значение, которого продукт произвести не умеет, обещает подсистему, которой нет.
const ( AuditOutboxStatusPending AuditOutboxStatus = "pending" AuditOutboxStatusSent AuditOutboxStatus = "sent" )
func (AuditOutboxStatus) Validate ¶
func (s AuditOutboxStatus) Validate() error
type BasicCredential ¶
type BasicCredential struct {
// PrincipalType — "user" | "service_account".
PrincipalType string
PrincipalID string
DisplayName string
// CredentialID — идентификатор СТРОКИ; им адресуется отзыв.
CredentialID string
ExpiresAt time.Time
}
BasicCredential — вердикт авторитета о годном предъявленном удостоверении.
type Cluster ¶
type Cluster struct {
ID ClusterID
Name ClusterName
Description Description
CreatedAt time.Time
}
Cluster — singleton (id = `cluster_root`). Корень иерархии cluster → account → project → resource. Используется как объект модели прав для `cluster:cluster_root#system_admin@user:usr_xxx`.
type ClusterAdminEntry ¶
type ClusterAdminEntry struct {
ClusterAdminGrantID string
SubjectType string
SubjectID string
SubjectEmail string
SubjectDisplayName string
GrantedByUserID string
GrantedByEmail string // "" when granted_by == "bootstrap"
GrantedAt time.Time
}
ClusterAdminEntry — read-projection of one active cluster-admin grant with denormalised user fields (subject email/display_name, granter email) resolved by the ListActive read-adapter's users JOIN. Pure domain view used by the InternalClusterService.ListAdmins use-case + handler; the pg adapter maps its rows INTO this type so the use-case/handler never import the pgx adapter package (Clean-Architecture dependency rule — the port speaks in domain types).
Denormalised fields (SubjectEmail / SubjectDisplayName / GrantedByEmail) are output-only mirrors; the authoritative subject/granter ids are SubjectID / GrantedByUserID.
type ClusterAdminGrant ¶
type ClusterAdminGrant struct {
ID ClusterAdminGrantID
ClusterID ClusterID
SubjectType GrantSubjectType
SubjectID SubjectID
GrantedBy string // 'bootstrap' либо user_id (verbatim text)
GrantedAt time.Time
GrantedUntil *time.Time // NULL = permanent
}
ClusterAdminGrant — permanent root grant. Один источник истины для FGA-tuple `cluster_admin`.
Partial UNIQUE (subject_type, subject_id) WHERE granted_until IS NULL гарантирует на DB-уровне, что **permanent** grant у одного subject — один. Temporary grants не входят в модель — есть только permanent.
func (ClusterAdminGrant) IsActive ¶
func (g ClusterAdminGrant) IsActive() bool
IsActive — true если grant — permanent active (`granted_until IS NULL`). False — для revoked / expired / time-bombed grants (granted_until set). Используется handler'ом и use-case'ами для diagnostic-веток.
func (ClusterAdminGrant) Validate ¶
func (g ClusterAdminGrant) Validate() error
type ClusterAdminGrantID ¶
type ClusterAdminGrantID string
ClusterAdminGrantID — self-validating newtype, format `cag_<17-crockford>`.
func (ClusterAdminGrantID) Validate ¶
func (id ClusterAdminGrantID) Validate() error
type ClusterID ¶
type ClusterID string
ClusterID — fixed literal `cluster_root` (singleton constraint в DB).
type ClusterName ¶
type ClusterName string
ClusterName — отображаемое имя singleton-кластера: длина 1..64 и БОЛЬШЕ ничего. Здесь стояло «kebab-case», и это было обещанием алфавита, которого проверка не делает ни в одной ветке.
Форму имени ресурса (`pkg/validate/nameform`) поле НЕ несёт намеренно: оно не задаётся клиентом — значение пишет посевная миграция, а у службы кластера нет глагола, который бы его менял.
func (ClusterName) Validate ¶
func (n ClusterName) Validate() error
type CountableKind ¶
type CountableKind struct {
Kind LimitKind
Carrier LimitCarrier
}
CountableKind — one catalogue record: WHAT is counted and WHERE it is counted.
func CountableEntries ¶
func CountableEntries() []CountableKind
CountableEntries returns a COPY of the closed catalogue, in catalogue order.
type CredentialKind ¶
type CredentialKind string
CredentialKind — ЧЕМ УДОСТОВЕРЕНИЕ СЕБЯ ПРЕДЪЯВЛЯЕТ (задача #1142, приёмка BAT-1 §2.5, §4.1).
Значение — то же, что лежит в колонке `credential_kind` обеих таблиц удостоверений: второго написания не заводится. Вид ЗАПИСЫВАЕТСЯ при вставке и читателем НЕ вычисляется — правило вывода по содержимому живёт ровно на одном пути (обратное заполнение существующих строк) и после него не применяется никогда.
const ( // CredentialKindUnspecified — вид не назван вызывающим. Встречается ТОЛЬКО // во входе выдачи и разрешается сохранённым поведением; в строке // удостоверения не бывает. CredentialKindUnspecified CredentialKind = "" // CredentialKindKeypair — ключевая пара ES256: вызывающий сам собирает и // подписывает `client_assertion` и обменивает его. CredentialKindKeypair CredentialKind = "KEYPAIR" // CredentialKindSecret — однострочный непрозрачный секрет, предъявляемый // как есть. CredentialKindSecret CredentialKind = "SECRET" // CredentialKindFederated — удостоверение предъявляет ВНЕШНИЙ издатель по // перечню доверенных субъектов; ни материала, ни секрета у нас нет. CredentialKindFederated CredentialKind = "FEDERATED" // CredentialKindLegacy — строка прежнего потока. НЕ ВЫДАЁТСЯ ни одним // глаголом; появляется только обратным заполнением. CredentialKindLegacy CredentialKind = "LEGACY" )
func ResolveIssuedKind ¶
func ResolveIssuedKind(asked CredentialKind, hasTrustedSubjects, federationSupported bool) (CredentialKind, error)
ResolveIssuedKind — КЛАССИФИКАТОР НАД ЗАПРОСОМ ВЫДАЧИ. Ветвей ТРИ, и дыры у него нет by construction: «ни материала, ни перечня» здесь означает KEYPAIR, потому что материал чеканим МЫ — строка получает его при выпуске.
Это НЕ тот классификатор, что работает над содержимым уже лежащих строк: тот четырёхветвевой, живёт в обратном заполнении и обязан иметь ветвь LEGACY, потому что материал чеканили не всегда мы. Спутать их значило бы получить «корзину прочее» наоборот — вход без вида не отвергается, а получает ближайший.
asked — вид, названный вызывающим (пустой = не назван). hasTrustedSubjects — непуст ли перечень доверенных субъектов запроса. federationSupported — есть ли у этого глагола поле, которым задаётся федеративный вид (у личности его нет, и вид недостижим by construction).
func (CredentialKind) IsIssuable ¶
func (k CredentialKind) IsIssuable() bool
IsIssuable отвечает, производит ли этот вид хоть один глагол выдачи. LEGACY не производит НИКТО — и это свойство самого вида, а не проверки в конкретном глаголе: иначе следующий глагол завёл бы под него выдачу.
func (CredentialKind) String ¶
func (k CredentialKind) String() string
String — значение как оно лежит в колонке.
type Description ¶
type Description string
func (Description) Validate ¶
func (d Description) Validate() error
type DisplayName ¶
type DisplayName string
func (DisplayName) Validate ¶
func (d DisplayName) Validate() error
type EffectiveLimit ¶
type EffectiveLimit struct {
Kind LimitKind
Value int64
// Carrier — ГДЕ этот вид считается: корень аренды (`project` / `account`)
// либо двухчастный токен родительского типа.
//
// Едет вместе с величиной, потому что вывести его на стороне потребителя
// НЕЛЬЗЯ: форма токена носителя не определяет (`iam.project` — двухчастный
// вид, чей носитель не проект), а догадка здесь не отказывает громко — она
// считает верные строки против неверного владельца, и потребление такой
// строки не наполняется никогда.
//
// Пустым не бывает: вид вне каталога до этой структуры не доходит.
Carrier LimitCarrier
SourceScope LimitScope
SourceScopeID string
}
EffectiveLimit — one resolved ceiling plus the scope it was won at.
The source travels with the value because an operator asking "why does this project stop at four" cannot otherwise tell a project override from an account one from the platform default without re-reading all three scopes by hand.
func ResolveEffective ¶
func ResolveEffective(service string, stated []Limit) []EffectiveLimit
ResolveEffective folds the limits stated across the three scopes into one row per kind of the requested service.
`stated` may contain rows of any scope in any order; only rows whose kind belongs to `service` participate. A kind with nothing stated at ANY scope is omitted from the answer — the caller must not read a missing row as "no ceiling": nothing was said, and inventing a ceiling here would be this function's guess rather than the platform's decision.
type EventTypeName ¶
type EventTypeName string
EventTypeName — `^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$` (CHECK).
func (EventTypeName) Validate ¶
func (n EventTypeName) Validate() error
type ExternalSubject ¶
type ExternalSubject string // OIDC sub
func (ExternalSubject) Validate ¶
func (s ExternalSubject) Validate() error
type FeedSource ¶
type FeedSource int8
FeedSource — where the reconciler reads candidate objects for a selectable type (D7).
const ( // FeedMirror — candidates read from kaname.resource_mirror (labels @> // matchLabels); containment from mirror.parent_* (D7). Mirror-fed types can be // PENDING_VERIFICATION (object not yet mirrored). Consumer-owned resources: // compute / vpc / loadbalancer. FeedMirror FeedSource = iota // FeedIAMDirect — candidates read SAME-DB from IAM's own resource table; // containment via iam-hierarchy (project ⊑ account ⊑ cluster). Never PENDING // (the object is always in its own table the instant it exists). Покрывает ВСЕ // iam-native типы (любой `iam.*`): account / project / user / serviceAccount / // group / role / accessBinding — все label-selectable под единой моделью. FeedIAMDirect )
func FeedSourceForType ¶
func FeedSourceForType(objectType string) FeedSource
FeedSourceForType classifies a selectable object type by its feed-source (D6/D7). An `iam.*` type is IAM-direct (own-table same-DB match, iam-hierarchy containment); every other selectable family (compute/vpc/loadbalancer) is mirror-fed. The classification is by module-prefix only — the precise selectable whitelist is enforced separately in the use-case (a non-selectable type never reaches the reconciler).
type GrantSubjectType ¶
type GrantSubjectType string
GrantSubjectType — enum: user|service_account (NOT group: cluster-admin grant — strictly individual identity for audit). Backed by migration 0011 CHECK.
const ( GrantSubjectTypeUser GrantSubjectType = "user" GrantSubjectTypeServiceAccount GrantSubjectType = "service_account" )
func (GrantSubjectType) Validate ¶
func (t GrantSubjectType) Validate() error
type Group ¶
type Group struct {
ID GroupID
AccountID AccountID
Name GroupName
Description Description
Labels Labels
CreatedAt time.Time
}
Group — Account-scoped (account_id FK ON DELETE RESTRICT), имеет members (User или ServiceAccount — полиморфно через `group_members.member_type`). Используется в AccessBinding для упрощения раздачи прав.
type GroupMember ¶
type GroupMember struct {
GroupID GroupID
MemberType SubjectType // user | service_account (group из enum'а исключен)
MemberID SubjectID
AddedAt time.Time
}
GroupMember — связка group_id ↔ (member_type, member_id). Целостность member_id обеспечивается DB-триггером `group_members_member_exists_trg` (нет полиморфного FK в Postgres).
func (GroupMember) Validate ¶
func (m GroupMember) Validate() error
type InteractiveClient ¶
type InteractiveClient struct {
ID InteractiveClientID
CreatedAt time.Time
Name InteractiveClientName
Description Description
Labels Labels
// RedirectURIs — where the provider may deliver an authorization code.
// At least one, every one an absolute https:// URL.
RedirectURIs []string
// PostLogoutRedirectURIs — optional; same https:// rule when present.
PostLogoutRedirectURIs []string
// ClientID — provider-side client id (output-only).
ClientID string
// Audiences — stamped by iam from its own configuration (output-only).
Audiences []string
// GrantTypes — always exactly authorization_code + refresh_token (output-only).
GrantTypes []string
// TokenEndpointAuthMethod — provider-side auth method (output-only).
TokenEndpointAuthMethod string
Status InteractiveClientStatus
}
InteractiveClient — the OAuth2 client through which a HUMAN completes an interactive sign-in ceremony.
Provider-side fields (ClientID, Audiences, GrantTypes, TokenEndpointAuthMethod) are OUTPUT-ONLY: they are decided by iam and the identity provider, never by the caller. Audiences in particular is a decision, not an omission — see Р2 in the acceptance: a field that can be set but cannot be set CORRECTLY is a field without a reader, and one that can be set incorrectly breaks the edge silently.
func (InteractiveClient) Validate ¶
func (c InteractiveClient) Validate() error
Validate — self-validating domain entity.
type InteractiveClientID ¶
type InteractiveClientID string
InteractiveClientID — id of an InteractiveClient. Hyphen canon `ic-<17>` (ids.PrefixInteractiveClientHyphen), immutable for the life of the resource and the only externally addressable identity (core rule #15).
type InteractiveClientName ¶
type InteractiveClientName string
InteractiveClientName — cluster-unique cosmetic label. Judged by the single resource-name form of the tree, so the DB CHECK and this validator agree.
func (InteractiveClientName) Validate ¶
func (n InteractiveClientName) Validate() error
Validate — единственная форма имени (`pkg/validate/nameform`).
type InteractiveClientStatus ¶
type InteractiveClientStatus string
InteractiveClientStatus — lifecycle status.
const ( InteractiveClientActive InteractiveClientStatus = "ACTIVE" InteractiveClientDeleting InteractiveClientStatus = "DELETING" )
InteractiveClientStatus values. Mirrored by a DB CHECK.
func (InteractiveClientStatus) Validate ¶
func (s InteractiveClientStatus) Validate() error
Validate — the status is one of the two named values.
type InviteStatus ¶
type InviteStatus string
InviteStatus — invite-flow state for a User row.
PENDING — created via `UserService.Invite`, external_id="" until first login; the invitee has not yet confirmed identity through Kratos. ACTIVE — either self-signup via `UpsertFromIdentity` without a pending invite, or a PENDING row activated on first-login (matched by email). BLOCKED — административный запрет на членство в Account'е. Ставится и снимается ДЕЙСТВИЯМИ `UserService.Block` / `Unblock` (право `identity_suspender@iam_user` = админ аккаунта плюс каскад облака; `v_update` с этого типа снят, #1128); писатель — `userWriter.SetInviteStatus`.
Состояние принадлежит СТРОКЕ ЧЛЕНСТВА, а не человеку. Одна личность держит по строке на каждый Account, поэтому запрет принадлежит тому аккаунту, который его наложил, и не отключает личность там, где она законно активна: выдача токена перебирает набор членств и обслуживает первое аутентифицирующееся, отказывая лишь когда ни одно не может (token_enrichment_service.go, iamhooks).
Снимать запрет самостоятельным действием нельзя: восстановление пароля доказывает владение почтовым ящиком — ровно то, чего администратор, ставя запрет, под сомнение не ставил (см. internal_on_recovery.go). Поэтому у пути блокировки ОБЯЗАН быть административный путь снятия, иначе заблокированный окажется заперт навсегда. Гейт blocked_state_reachability_test.go требует, чтобы каждый писатель этого состояния был объявлен вместе со ссылкой на снятие, — и делает появление одностороннего пути упавшей сборкой, а не открытием.
const ( InviteStatusPending InviteStatus = "PENDING" InviteStatusActive InviteStatus = "ACTIVE" InviteStatusBlocked InviteStatus = "BLOCKED" )
func (InviteStatus) MayAuthenticate ¶
func (s InviteStatus) MayAuthenticate() bool
MayAuthenticate reports whether a user in this state may be issued a token.
This is the VERDICT both token hooks ask for, and the reason it exists as a predicate rather than as a WHERE clause: the hooks used to resolve the identity through an ACTIVE-only query, which turns "blocked" into "absent" — and then read absence in opposite directions. One refused; the other took it for "the mirror has not committed yet" and issued the reduced claim set to a blocked user. A filter cannot be asked "why"; a verdict can.
Only ACTIVE authenticates. PENDING is an invitee who has not confirmed an identity yet (the DB CHECK users_invite_status_consistency keeps such a row from carrying an external id at all, so it is a floor rather than a live path), and an unset state is not an authorisation.
func (InviteStatus) Validate ¶
func (s InviteStatus) Validate() error
type Limit ¶
type Limit struct {
ID LimitID
CreatedAt time.Time
Scope LimitScope
ScopeID string
Kind LimitKind
Value int64
// WithdrawnAt — the moment the ceiling stopped applying. Zero → in force.
//
// A withdrawal is a tombstone rather than a deleted row because owner
// services keep a PROJECTION of these values and refresh it by delta: a delta
// that only ever reports writes can never drop a projection row, so a
// withdrawn project override would keep overriding forever.
WithdrawnAt time.Time
// Revision — monotonic, assigned by the database. Advances on a change of
// value or of withdrawal, and stands still when a write restates what was
// already there.
Revision int64
}
Limit — the ceiling on how many resources of one kind a tenant may hold.
The triple (Scope, ScopeID, Kind) IS the limit's identity among those in force; `Value` is the only mutable field. "Moving" a ceiling to another project or another kind is a different ceiling, created and withdrawn explicitly — an Update that could change the triple would silently transfer a tenant's headroom to a tenant who was never granted it.
func (Limit) Validate ¶
Validate — self-validating domain entity.
The scope/subject pairing is checked HERE as well as by a DB CHECK, and that is not a duplicate rule: the database makes the state inexpressible for every writer, and this makes the caller's answer name the FIELD they got wrong instead of a constraint they have never heard of.
type LimitCarrier ¶
type LimitCarrier string
LimitCarrier — the type of object a kind is counted IN.
WHY IT IS DECLARED AND NOT DERIVED. The temptation is "two parts ⇒ counted in a project", and that rule is false on the first entry that already exists: `iam.project` has two parts and is counted in an ACCOUNT, because a project does not live inside a project. A guess here does not fail loudly — it counts the right rows against the wrong owner, and the tenant sees a ceiling that never moves. So the carrier travels beside the kind, and the pair is the unit of the catalogue.
const ( // CarrierProject — counted per project. The common case. CarrierProject LimitCarrier = "project" // CarrierAccount — counted per account, for kinds that have no project to // live in. // // НИ ОДИН ВИД КАТАЛОГА ЕГО СЕГОДНЯ НЕ НАЗЫВАЕТ, и это состояние, а не // упущение: шестеро, кто им пользовался, — проекты и субъекты в области // аккаунта — сняты с каталога вместе со своим учётом // (`PRO-Robotech/kacho#2117`, сценарий `KAN-Q3-04`), потому что не // списывались ни разу. // // Константа ОСТАЁТСЯ, и снимать её этим изменением нельзя: величину // `'account'` по-прежнему принимает ограничение // `project_resource_quotas_carrier_ck` ПРИМЕНЁННОЙ миграции, а править // применённую нельзя (ban #5). Уходит она вместе с остальным механизмом // величин — стадией S4 той же приёмки, где снимаются таблицы и функции. CarrierAccount LimitCarrier = "account" // CarrierIdentity — counted per HUMAN, across every account they hold. // // # Why a third root had to exist // // A carrier must be EXTERNAL to the thing it counts, and for the account // neither of the two above is: an account cannot be counted inside an // account, and it has no project. That is not an implementation gap but the // shape of the tenancy: the account is its root, and the root has no parent // below the cluster. // // Counting per cluster was the obvious alternative and it is worse in the way // that matters: the refusal reaches the NEXT honest tenant rather than the one // who exhausted the shelf. The identity is the only thing that exists BEFORE // an account and outlives it, so it is the only carrier on which the refusal // lands on its cause. // // # What identifies it, and why not the user row // // The identity is the external login subject (`users.external_id`), NOT the // user row. A user row is a MEMBERSHIP: it is scoped to one account, and one // human legitimately holds one per account. Counting per user row would tie // the ceiling to the very thing that multiplies as soon as the account // coupling is removed — that is, it would hand out the bypass together with // the change it is meant to survive. CarrierIdentity LimitCarrier = "identity" )
The carriers that are not resource kinds: the tenancy roots. Any other carrier is a two-part token of the closed table (`vpc.network`), naming the parent a nested kind is counted within.
func CarrierOfKind ¶
func CarrierOfKind(k LimitKind) (LimitCarrier, bool)
CarrierOfKind returns the carrier a kind is counted in. The second result is false for a kind outside the catalogue — and the caller must not read a missing carrier as "project": that default is exactly the guess V2-2 forbids.
func (LimitCarrier) Validate ¶
func (c LimitCarrier) Validate() error
Validate — the carrier names one of the tenancy roots, or is shaped like a two-part catalogue token.
That the token RESOLVES against the authorization model is proved by authzmap's gate, not here: this package must not import the authz map (that package's gate already imports this one), and a second copy of the closed table here would be the two-places-one-subject class the corpus warns about.
type LimitFilter ¶
type LimitFilter struct {
Scope LimitScope
ScopeID string
Kind LimitKind
}
LimitFilter — the three-valued narrowing a List accepts. Empty members mean "do not narrow by this"; the vocabulary is closed, so there is no filter grammar here and nothing to parse.
func (LimitFilter) Validate ¶
func (f LimitFilter) Validate() error
Validate — a narrowing value that is not a legal value of its dimension is refused by the field's NAME rather than silently matching nothing: a filter that quietly returns an empty page is indistinguishable from "there is nothing here".
type LimitID ¶
type LimitID string
LimitID — id of a Limit. Hyphen canon `lim-<17>` (ids.PrefixLimitHyphen), immutable for the life of the resource and the only externally addressable identity (core rule #15).
type LimitKind ¶
type LimitKind string
LimitKind — a dotted token naming what is being counted. Two forms, and only two:
`<domain>.<resource>` — a resource counted in its carrier `<domain>.<parent>.<child>` — how many <child> fit in ONE <parent>
Both forms name REAL types of the authorization model, and the three-part form names two of them. That is a gate rather than a convention: a ceiling stated on a name the platform does not know is a ceiling nobody can check and nobody can show the tenant (§7 п.9 of the acceptance).
func AccountScopedKinds ¶
func AccountScopedKinds() []LimitKind
AccountScopedKinds отдаёт КОПИЮ перечня в устойчивом порядке.
func AuthorityStatedKinds ¶
func AuthorityStatedKinds() []LimitKind
AuthorityStatedKinds — виды каталога, чью величину назначает ВНЕШНИЙ авторитет.
ВЫВОДИТСЯ из каталога вычетом, а не выписывается вторым списком: выписанный разошёлся бы с каталогом молча при первом же новом виде.
func CountableKinds ¶
func CountableKinds() []LimitKind
CountableKinds returns just the kinds of the catalogue, in catalogue order.
func CountableKindsOfService ¶
CountableKindsOfService returns the catalogue entries owned by one service, in catalogue order. An unknown service yields an empty slice — and the caller must treat that as "this service counts nothing", not as "every kind".
func PostureStatedKinds ¶
func PostureStatedKinds() []LimitKind
PostureStatedKinds — копия закрытого множества, в порядке объявления.
func (LimitKind) ChildKind ¶
ChildKind — the two-part token of the child a nested kind counts; empty for a flat kind. `vpc.network.subnet` → `vpc.subnet`.
The child's domain is the kind's domain: a nested kind never crosses a service boundary, because the parent and the child are rows of one database and the count is an invariant of one schema (data-integrity §within-service).
func (LimitKind) Nested ¶
Nested reports whether this kind bounds children within ONE parent (`vpc.network.subnet`) rather than within the carrier as a whole.
func (LimitKind) ParentKind ¶
ParentKind — the two-part token of the parent a nested kind counts within; empty for a flat kind. `vpc.network.subnet` → `vpc.network`.
This is the token that must resolve against the closed table, and it is returned rather than re-derived at each call site so the two halves of the three-part gate cannot disagree about where the split is.
func (LimitKind) Service ¶
Service — the owner service this kind belongs to (`vpc.network` → `vpc`, `vpc.network.subnet` → `vpc`).
Derived from the token rather than stored beside it: two fields naming one thing drift, and the dot is the same separator the platform's reference types already use.
func (LimitKind) Validate ¶
Validate — membership in the closed catalogue, refused by the field's name.
Membership is the only check needed: the catalogue admits two shapes and no others, and every entry in it is proved well-formed and type-resolvable by authzmap's gates. A token of four parts, or of two parts naming nothing, is simply not a member.
type LimitScope ¶
type LimitScope string
LimitScope — where a ceiling is stated. Three arms, ordered by specificity: PROJECT beats ACCOUNT beats DEFAULT.
const ( // LimitScopeDefault — platform-wide fallback; exactly one row per kind. LimitScopeDefault LimitScope = "DEFAULT" // LimitScopeAccount — stated for one account. LimitScopeAccount LimitScope = "ACCOUNT" // LimitScopeProject — stated for one project; the most specific arm. LimitScopeProject LimitScope = "PROJECT" )
LimitScope values. Mirrored by a DB CHECK.
func (LimitScope) Specificity ¶
func (s LimitScope) Specificity() int
Specificity — how strongly this scope overrides the others. Higher wins.
It is a METHOD rather than a comparison written at each resolve site: the precedence rule is one rule, and three places asserting it independently would disagree the first time a fourth scope appeared.
func (LimitScope) Validate ¶
func (s LimitScope) Validate() error
Validate — the scope is one of the three named arms.
type Membership ¶
type Membership struct {
ID MembershipID
AccountID AccountID
// AccountName — ЗЕРКАЛО имени аккаунта, источник истины сам аккаунт.
// Заполняется на чтении соединением в той же БД; пустое значение означает
// «имя не задано», а НЕ «аккаунта нет».
AccountName AccountName
UserID UserID
State MembershipState
// InvitedBy — кто пригласил. Это и есть след приглашения, ПЕРЕЖИВАЮЩИЙ вход:
// активация трогает состояние и отметку правки и не трогает это поле.
// Пусто у членства, заведённого не приглашением.
InvitedBy UserID
// CreatedAt — когда членство заведено; для приглашения — когда оно выписано.
CreatedAt time.Time
// UpdatedAt — когда состояние менялось в последний раз.
UpdatedAt time.Time
}
Membership — принадлежность человека аккаунту как ОТДЕЛЬНАЯ связь.
Ресурс ВЫХОДНОЙ: глаголов создания, правки и снятия у него на читающей поверхности нет. Заводит членство приглашение, снимает — исключение из аккаунта, и оба принадлежат потоку человека.
type MembershipID ¶
type MembershipID string
MembershipID — идентификатор членства, дефис-форма `mbr-<17>`.
Он ВЫЧИСЛЯЕТСЯ из пары «человек × аккаунт» неизменяемой SQL-функцией без соли, а не чеканится: писателей строки членства больше одного, и все обязаны прийти к ОДНОЙ строке на одну пару. Отсюда два следствия, которые здесь названы, потому что они меняют решения читающего кода:
- идентификатор ПЕРЕИСПОЛЬЗУЕТСЯ. Уникальность пары полная, снятие есть удаление строки, функция неизменяема — значит повторное приглашение того же человека в тот же аккаунт вернёт ТОТ ЖЕ идентификатор. Признак «строка заведена заново» — `CreatedAt`, а не `ID`;
- идентификатор ВЫЧИСЛИМ ПОСТОРОННИМ. Зная человека и аккаунт, его получает кто угодно, ни разу не обратившись к платформе. Поэтому одиночное чтение обязано быть суженным по аккаунту в САМОМ ЗАПРОСЕ: без этого адрес чужого членства строится арифметикой, а не перебором.
type MembershipState ¶
type MembershipState string
MembershipState — состояние членства. Значений ровно два, и словарь закреплён CHECK'ом на таблице: третьего в колонке не появится.
Согласованность состояния членства со статусом человека конструкцией НЕ закреплена — её держат писатели (зеркалящий триггер строки человека и два стейтмента репозитория). Читающий код поэтому судит то, что в строке лежит, и не выводит одно из другого.
const ( // MembershipStatePending — человек приглашён и ещё не входил. // // Наблюдаемо АККАУНТ-СКОУПНЫМ чтением и не наблюдаемо своим списком: у // приглашённого нет внешнего субъекта, значит нечем аутентифицироваться, // значит своего чтения он вызвать не может. Это не расхождение проекций, а // арифметика — разные наблюдатели видят разное. MembershipStatePending MembershipState = "PENDING" // MembershipStateActive — человек состоит в аккаунте. MembershipStateActive MembershipState = "ACTIVE" )
type MembershipTuple ¶
type MembershipTuple struct {
User string // e.g. "user:usr-…", "group:grp-…#member"
Relation string // tier relation (e.g. "editor")
Object string // e.g. "compute_instance:inst-1"
}
MembershipTuple — a per-object FGA relation tuple the reconciler emits/revokes for a materialized membership (subject → tier → object). A flat value so the reconcile use-case stays transport/storage-agnostic; the pg adapter maps it to the fga_outbox payload.
type MirrorObject ¶
type MirrorObject struct {
ObjectType string
ObjectID string
ParentProjectID string
// ParentAccountIDs — АККАУНТЫ, под которыми лежит объект. Множественное
// число здесь не запас на будущее: у ЛИЧНОСТИ принадлежность аккаунту
// перестала быть свойством её строки и стала отдельной связью, которых у
// человека бывает несколько (#470/#471), поэтому скалярное поле называло бы
// ОДИН аккаунт из многих — и выдача второго аккаунта не находила бы своего
// человека by construction (#1172).
//
// У всех прочих типов набор вырожден: ноль элементов (объект не лежит ни в
// одном аккаунте) либо один. Пустые строки в набор НЕ КЛАДУТСЯ — «аккаунта
// нет» выражается отсутствием элемента, а не элементом-пустышкой, иначе
// область с пустым идентификатором совпала бы с ним.
ParentAccountIDs []string
Labels map[string]string
}
MirrorObject — the same-DB parent-scope projection of an owner object read from resource_mirror (β fed it; γ reads it). Pure value; the reader adapter fills it.
func (MirrorObject) IsContainedIn ¶
func (m MirrorObject) IsContainedIn(scope ScopeAnchor) bool
IsContainedIn reports whether a mirror object lies UNDER the given scope-anchor (the single containment predicate for byName AND byLabel — parity).
project:P ⊑ project:P (same project) project:P ⊑ account:A if A ∈ mirror.ParentAccountIDs any ⊑ cluster:* (cluster contains everything)
A cluster-scoped binding contains every registered object. The cluster id is not compared (there is a single cluster root in the FGA model).
This predicate is PURE (no DB): it trusts ParentAccountIDs to already carry the object's FULL account set. For a mirror-fed object registered with only its owning PROJECT, the reader adapter resolves the account through the project→account hierarchy same-DB (resource_mirror reader COALESCE) BEFORE filling ParentAccountIDs, so an account-scoped binding transitively contains an object nested in a project of its account even when the stored parent_account_id column was empty. The resolution is account-bounded (one project → one account), so this predicate never leaks across the account boundary.
ЧЛЕНСТВО ЗДЕСЬ — «хотя бы одно», и это не послабление. У личности аккаунтов столько, сколько у человека членств (#1172): выдача аккаунта B накрывает его потому, что он состоит в B, и НИ ОДИН чужой аккаунт от этого его не накрывает — набор перечисляет ровно те аккаунты, связь с которыми существует строкой `kaname.memberships`.
Пустая область (scope.ID == "") не совпадает ни с чем: набор пустых строк не содержит by construction (см. комментарий поля), но проверка стоит здесь же — сравнение «пусто с пусто» иначе давало бы истину на объекте без аккаунта.
func (MirrorObject) MatchesLabels ¶
func (m MirrorObject) MatchesLabels(matchLabels map[string]string) bool
MatchesLabels reports whether the mirror object's labels satisfy the AND-equality match set: for EVERY (k,v) in matchLabels the object must have labels[k]==v (superset allowed — the object may carry extra labels). This is the in-Go equivalent of the JSONB `labels @> matchLabels` probe used by the reader's SQL filter; kept here so the containment verdict is testable in pure domain and the reconciler can re-assert it on a candidate set.
type ModuleSet ¶
type ModuleSet interface {
// IsKnownModule — состоит ли модуль в наборе.
IsKnownModule(module string) bool
}
ModuleSet — членство в наборе модулей платформы, каким его знает ВЫЗЫВАЮЩИЙ.
Подстановочный знак `*` членом набора не является ни в одной реализации: он не имя модуля, а маркер политики, и разбирается Rule.Validate отдельно.
func ModuleSetOf ¶
ModuleSetOf — набор из перечня имён, для вызывающего, у которого перечень уже в руках: канон дерева, фикстура пробы, применитель ролей модуля.
Пустой перечень даёт набор, не признающий НИЧЕГО, и это не вырожденный случай, а тот же fail-closed, что у отсутствующего набора ниже: «перечень пуст» не есть «принимаем любой». Разница лишь в том, что здесь отказ приходит с именем модуля, а там — с указанием на непровязанный источник.
type OAuthClientID ¶
type OAuthClientID string
OAuthClientID — opaque hydra client id (length 1..128, [A-Za-z0-9._:-]).
func (OAuthClientID) Validate ¶
func (h OAuthClientID) Validate() error
type OAuthClientName ¶
type OAuthClientName string
OAuthClientName — человекочитаемое имя токена (SA-key / user-token). Судится единственной формой имени дерева, как и остальные имена iam.
Пустая строка остаётся законным ВХОДОМ выпуска и означает «назови сам»: до записи её заменяет имя, производное от идентификатора (`validate.NameOrDefault` в use-case). Здесь пустое уже не проходит — эта проверка судит то, что БУДЕТ ЗАПИСАНО, а записи с пустым именем не бывает.
func (OAuthClientName) Validate ¶
func (n OAuthClientName) Validate() error
Validate — OAuthClientName судится той же формой, что и остальные имена.
Прежде здесь стояла ветка «пустое допустимо», и она была единственным местом iam, где имя доживало до записи пустым. Пустое имя — не «имя, которого нет», а ресурс, который не ищется, не отличается в списке и показывается прочерком. Ветка снята: пустое заменяется умолчанием ДО записи, в use-case выпуска.
type OperationID ¶
type OperationID string
type Permission ¶
type Permission string
func (Permission) Validate ¶
func (p Permission) Validate() error
Validate — Permission: one element with wildcard semantics.
type Permissions ¶
type Permissions []Permission
func CompileRules ¶
func CompileRules(rules []Rule) (Permissions, error)
CompileRules deterministically compiles authored rules into the INTERNAL compiled permission set (4-segment `module.resource.resourceName.verb`), for FGA emission/Check-reuse. Pure + deterministic:
- ARM_ANCHOR → `m.r.*.v` for each (resource, verb) over the rule's module.
- ARM_NAMES → `m.r.<id>.v` for each (resource, id, verb) over the module.
- ARM_LABELS → NOT compiled (excluded).
- verb `*` → projection keeps the `*` verb segment (`m.r.*.*` / `m.r.<id>.*`), it does NOT expand the closed per-verb set (that is the FGA-emit concern). The projection holds `*`.
The result is de-duplicated (order-preserving over first occurrence) and capped at MaxCompiledPermissions; exceeding the cap → INVALID_ARGUMENT "compiled permissions exceed 1024" (not silent truncation, not INTERNAL).
The projection is system-context-independent (a system role's `*`-segments are authored verbatim into the rules and projected as-is), so CompileRules takes no systemCtx flag — the wildcard/system policy is enforced upstream by Rule.Validate.
func (Permissions) Validate ¶
func (p Permissions) Validate() error
Validate — Permissions: cardinality 1..1024; each — Permission.Validate.
The ≥1 lower bound applies to the LEGACY permissions-only role path (a role authored as a bare permission set must carry at least one). Cap raised 256→1024 in lockstep with the DB CHECK iam_permissions_valid (migration 0025) and the proto (size) bound, so the compiled-permission set derived from a role's rules (CompileRules, ≤MaxCompiledPermissions) always passes domain + DB validation (acceptance R-12 / A-12). A rules-role uses ValidateCompiled (no lower bound) — a label-only role compiles to an EMPTY set by design (ARM_LABELS excluded).
func (Permissions) ValidateCompiled ¶
func (p Permissions) ValidateCompiled() error
ValidateCompiled validates the INTERNAL compiled-permission projection of a rules-role: the 4-segment grammar parity + the ≤1024 cap, but WITHOUT the ≥1 lower bound. A rules-role whose rules are ALL ARM_LABELS compiles to an EMPTY permission set (matchLabels is not compiled — R-7 / fix #8), which is valid; the authority lives in rules[], not permissions[]. Only the legacy permissions-only path keeps the ≥1 floor (Validate). Acceptance A-10 (label-only positive).
type PrivilegeDerivation ¶
type PrivilegeDerivation string
PrivilegeDerivation — how a subject obtained a privilege.
const ( // DerivationDirect — the binding's subject IS the requested subject. Also the // zero value's meaning (see SubjectPrivilege.Derivation). DerivationDirect PrivilegeDerivation = "" // DerivationGroup — the binding's subject is a GROUP the requested subject is // a member of. Groups do not nest, so this is exactly one hop. DerivationGroup PrivilegeDerivation = "GROUP" )
type Project ¶
type Project struct {
ID ProjectID
AccountID AccountID
Name ProjectName
Description Description
Labels Labels
CreatedAt time.Time
}
Project — child Account-а («folder» в YC-стилистике, но без промежуточного Cloud). Уникальное имя per-Account (DB UNIQUE projects_account_name_unique).
FK: account_id → accounts(id) ON DELETE RESTRICT. account_id — hard-immutable после Create (Update его reject'ит).
func (Project) StructuralFacts ¶
func (p Project) StructuralFacts() []StructuralTuple
StructuralFacts returns the project's cluster and account pointers — both of which `project.super_admin: admin from account or any_admin from cluster` reads.
type ProjectName ¶
type ProjectName string
func (ProjectName) Validate ¶
func (n ProjectName) Validate() error
type PrunedSelectorType ¶
type PrunedSelectorType struct {
// ObjectType — точечный тип объекта, чью строку каталога сняли.
ObjectType string
// Outcome — что стало со строкой отбора, из которой тип вырезан.
Outcome SelectorPruneOutcome
// Reason — причина снятия строки каталога, записанная применителем
// манифеста. Причина ПЛАТФОРМЫ, а не действие арендатора: своё правило он не
// менял. ПУСТАЯ строка означает «строка каталога причины не несла» (снята
// ранней миграцией), а не «причину потеряли».
Reason string
// PrunedAt — момент вырезания. Время ТРАНЗАКЦИИ применения: у всего, что
// вырезано одним применением, он совпадает дословно.
PrunedAt time.Time
// AppliedBy — АВТОР применения, вырезавшего этот тип (#2005). Та же величина
// и тот же источник, что у соседа: вопрос «кто снял» у обеих ведомостей
// общий — арендатор не различает, какой из проекций правила он лишился.
//
// ПУСТАЯ строка означает «строка вырезана ДО заведения колонки».
AppliedBy string
}
PrunedSelectorType — точечный тип, ВЫРЕЗАННЫЙ применителем каталога из отбора правила роли при снятии строки ресурса платформы (#1988).
Чем это отличается от соседней [WithdrawnGrant], и почему они не сложены ¶
Переселение адресуется парой «тип объекта + глагол» и означает «право по этой паре действовало и перестало». Вырезание глагола не имеет ВООБЩЕ: отбор правила называет типы, а не пары, — поэтому у него своя ведомость и свой тип здесь. Сложить их значило бы дать вырезанному пустой глагол, а пустой глагол в соседе есть ЯКОРЬ объявления правила, то есть уже занятое значение.
Отпечаток правила сюда НЕ ПЕРЕЕЗЖАЕТ ¶
Ведомость ключуется тройкой «роль + отпечаток правила + тип»: отпечаток нужен ХРАНЕНИЮ, чтобы два правила, потерявшие один тип, не схлопнулись в одну строку. Читающему он не нужен и не отдаётся — это содержательный хеш, которого нет ни в одном контракте платформы; выставь мы его, способ хеширования стал бы частью контракта, а арендатор своё правило знает содержанием, а не дайджестом.
Следствие названо прямо: строки, различавшиеся ТОЛЬКО отпечатком, для читающего есть ОДИН факт и произносятся один раз.
type PublishedKey ¶
type PublishedKey struct {
KID KeyID
Algorithm SigningAlgorithm
PublicKeyPEM string
}
PublishedKey — ПУБЛИКУЕМАЯ форма ключа.
Поля приватной половины у этого типа НЕТ и быть не может (F1-05): положить её сюда не выражается, а не «запрещено правилом».
type RecoveryCompletion ¶
type RecoveryCompletion struct {
RecoveryJTI string
ExternalID ExternalSubject
UserID UserID
RevokedSessionCount int32
}
RecoveryCompletion — one row of the Kratos recovery-completed idempotency ledger (migration 0015). PK recovery_jti dedups at-least-once webhook delivery. The row stores the deterministic primary user_id (first row by created_at ASC) and the revoked session count so a duplicate delivery can replay the same Operation.metadata without re-running any side-effect.
func (RecoveryCompletion) Validate ¶
func (r RecoveryCompletion) Validate() error
Validate — self-validating domain entity. Length bounds mirror the migration CHECK constraints + the proto field annotations.
type ResourceRef ¶
ResourceRef is the closed-table {type,id} per-object pointer (redesign-2026 F8 / B1). Unlike a generic Referrer it carries NO name — the iam target is the strict {type,id} form. `Type` is the dotted `<module>.<resource>` key.
type ResourceType ¶
type ResourceType string
ResourceType — ЯКОРЬ ОБЛАСТИ привязки: один из трёх ярусов иерархии (`cluster`/`account`/`project`). Вокабуляр объявлен ОДИН раз — `scopeAnchorTiers` в access_binding_scope.go, — и `Validate()` судит по нему. Пообъектный тип якорем не бывает: он живёт на оси `target` (F8).
func (ResourceType) Validate ¶
func (r ResourceType) Validate() error
Validate — ResourceType — ярус иерархии, и только он.
Судит ЕДИНСТВЕННОЕ объявление вокабуляра якоря (`scopeAnchorTiers`), а не своя копия: копия здесь была, разошлась с ним на 20 записей и принимала пообъектные типы, которых не производит ни один путь записи. Разбор — у самого объявления.
type Role ¶
type Role struct {
ID RoleID
ClusterID ClusterID // set for system role
AccountID AccountID // set for account-scoped custom
ProjectID ProjectID // set for project-scoped custom
Name RoleName
Description Description
// Rules — authored policy (RBAC rules-model 2026). Source of truth + public
// API surface. Compiled into Permissions (internal, FGA-emit) by CompileRules.
Rules Rules
// Permissions — INTERNAL compiled form (anchor/names arms; match_labels NOT
// compiled). Derived from Rules via CompileRules; NOT a public API field for
// rules-roles. Legacy permissions-only roles (no Rules) keep their stored set.
Permissions Permissions
IsSystem bool
// OwnerModule — модуль, которому роль принадлежит. Пусто у ПЛАТФОРМЕННОЙ
// роли (admin/edit/view/owner, kacho-system.*), непусто у роли, объявленной
// манифестом этого модуля.
//
// Носит ровно ОДИН смысл — владение, — и потому отделяет послабление
// подстановки от кластерного якоря: `IsSystem` продолжает означать «арендатор
// эту роль не правит» и больше не означает «этой роли можно подставлять
// звёздочку». Политика выводится из пары ОДНИМ местом — [PolicyOfRole].
//
// Наружу не проецируется: `Role` публичного контракта этой колонки не несёт
// (задача продукта #1032, тип изменения — ВВОДЯЩЕЕ, без нового поля API).
OwnerModule string
CreatedAt time.Time
// CreatedByUserID — authoring principal (governance/audit). Optional.
CreatedByUserID UserID
// UpdatedAt — last-mutation timestamp. Zero until first Update.
UpdatedAt time.Time
// Labels — tenant-facing метки САМОГО ресурса Role (НЕ путать с
// Rule.MatchLabels, отбирающим объекты под грантом). Делают Role
// label-selectable наравне с account/project (ARM_LABELS-грант на iam.role →
// v_list по `labels @> matchLabels`; List фильтрует viewer ∪ v_list).
Labels Labels
// Integrity — целость роли: даёт ли она то, что объявляет (#1035).
//
// ВЫВОДИТСЯ НА ЧТЕНИИ и НЕ ХРАНИТСЯ: колонки у неё нет, в перечень колонок
// писателя она не входит и [Role.Validate] её не судит. Нулевое значение
// означает «этим путём не вычислено» — так её и видят пути, которые роль не
// читают, а возвращают эхом мутации.
Integrity RoleIntegrity
// Lifecycle — ОБЪЯВЛЕНА роль манифестом модуля сегодня либо СНЯТА (#1913).
//
// Отдельно от `Integrity` рядом, и различие несущее: `RoleHealthEmpty` даёт
// и снятая роль, и объявленная, чьи строки каталога сняты, — а следующий шаг
// у арендатора разный. Довод целиком — [RoleLifecycle].
//
// ПРИХОДИТ ЧТЕНИЕМ: колонки пометки у строки есть, но заполняют это поле
// `Get` и `List`, а ответ операции его не несёт — нулевое состояние там
// означает «этим ответом не вычислено», ровно как у соседа.
Lifecycle RoleLifecycle
// Withdrawn — ЧТО у роли отобрано и почему (#1992): строки ведомости
// переселения. ОБЪЯСНЯЕТ состояние целости и не определяет его — у роли,
// пострадавшей третьим путём, переселения не было вовсе, и список пуст при
// нездоровом состоянии.
//
// Пустой срез означает «отобранного нет», а не «не читали»: разводит их
// [RoleIntegrity.Health] рядом, у которого нулевое состояние есть «этим
// ответом не вычислено».
Withdrawn []WithdrawnGrant
// PrunedSelectorTypes — какие точечные типы ВЫРЕЗАНЫ из отбора правил этой
// роли и почему (#1988): строки ведомости вырезания.
//
// Сосед [Role.Withdrawn] отвечает про ДВЕ проекции правила, у которых есть
// пара «тип + глагол»; эта — про ТРЕТЬЮ, где глагола нет вовсе. Разные
// ведомости, разные события, поэтому два поля, а не одно.
//
// Пустой срез означает «вырезанного нет», а не «не читали»: разводит их
// [RoleIntegrity.Health] рядом, у которого нулевое состояние есть «этим
// ответом не вычислено».
PrunedSelectorTypes []PrunedSelectorType
// RuleStates — состояние КАЖДОГО правила роли (#1962): действует, отозвано
// платформой либо не разрешилось. Записей ровно по числу правил.
//
// Отвечает на вопрос, которого нет ни у `Integrity`, ни у `Withdrawn`: КАКОЕ
// правило пострадало и по КАКОЙ ИЗ ДВУХ причин. Картина счётчиков у причин
// ОДИНАКОВА, а следующий шаг арендатора — разный.
//
// ПРИХОДИТ ЧТЕНИЕМ, НЕ ХРАНИТСЯ — тем же доводом, что `Integrity` рядом.
// ПУСТОЙ СРЕЗ означает «этим путём не вычислено» и законен: ответ операции
// состояния не несёт. У роли БЕЗ правил он пуст и на чтении — состояние есть
// свойство правила, и у роли без правил его нет.
RuleStates []RuleState
// TypeVerbs — набор глаголов ТИПА, каким его объявляет ЖИВАЯ строка каталога
// (#1994). Тем же набором идёт материализация, поэтому превью и эмиссия не
// могут разойтись.
//
// ПРИХОДИТ ПРОВЯЗКОЙ, НЕ ХРАНИТСЯ — как и `Integrity` рядом: колонки у неё
// нет, писатель её не пишет, [Role.Validate] её не судит. Отличие от
// `Integrity` в том, чем является НУЛЕВОЕ значение: там оно означает «этим
// путём не вычислено» и законно, здесь — ОТКАЗ проекции. Показ по словарю,
// порождённому сборкой, неотличим от честного превью, поэтому «нечем
// ответить» обязано быть отказом, а не тихим запасным путём.
TypeVerbs TypeVerbLookup
}
Role — multi-scope. Exactly one scope field is non-NULL:
- is_system=true + ClusterID set: system role (`kacho-system.admin`, ...).
- is_system=false + AccountID set: account-scoped custom role.
- is_system=false + ProjectID set: project-scoped custom role.
Enforced by DB CHECK `roles_definition_tier_xor` + a partial UNIQUE per scope. Domain.Validate duplicates the CHECK to give friendly errors before reaching the DB. (The legacy B2B-tenant role scope was removed; a custom role is scoped to exactly one of {account, project}.)
func (Role) AuthoredVerbs ¶
func (r Role) AuthoredVerbs(lookup TypeVerbLookup) []string
AuthoredVerbs is the deduped, canonically-ordered union of the role's rule verbs (`*` expands to the verb set of the addressed type). Empty for a label-only / rules-less role.
func (Role) CanonicalRank ¶
CanonicalRank returns the ordering rank of a canonical system role (0=viewer … 3=owner) so the catalog can present the four first among system roles; a non-canonical role sorts after them.
func (Role) DefinitionTierID ¶
DefinitionTierID returns the anchor object id of the role's definition tier (cluster / account / project id), matching DefinitionTierType.
func (Role) DefinitionTierType ¶
DefinitionTierType returns the dotted definition-tier type of the role: iam.cluster for a system role (cluster_id set), iam.account / iam.project for a custom role. Empty when no anchor is set (should not happen under the XOR CHECK).
func (Role) DisplayName ¶
DisplayName is the friendly catalog label. For a canonical system role it is the curated name (Viewer/Editor/Admin/Owner); otherwise it defaults to the role name.
func (Role) EffectiveVerbs ¶
func (r Role) EffectiveVerbs(lookup TypeVerbLookup) []string
EffectiveVerbs is AuthoredVerbs plus the editor `delete*` qualifier for an editor-tier role.
func (Role) IsClusterAdminRole ¶
IsClusterAdminRole reports whether r is THE system cluster-admin superuser role.
It is identified by its PINNED deterministic id (ClusterAdminRoleID `admin` or SystemAdminRoleID `kacho-system.admin`) AND is_system AND (defence-in-depth) that it still carries the full `*.*.*` ARM_ANCHOR rule (module:*, `*` resource, `*` verb). Matching by id — NOT by the bare `*.*.*` shape — is load-bearing for #8: the `owner` system role (OwnerRoleID, migration 0035) carries the SAME `*.*.*` shape, so a shape-only predicate would misclassify owner as cluster-admin and let an owner@GLOBAL+all binding slip past the A-05 reject. owner is auto-bound at ACCOUNT scope only and is NOT the GLOBAL+all exception.
This is the ONLY role for which a GLOBAL+all binding is legal (A-05c) — its binding is materialized as the D-9 cluster-relation, not per-object.
func (Role) IsSystemDerived ¶
IsSystemDerived reports whether the role is a system role, DERIVED from its definition tier (tierType == iam.cluster) rather than a stored provenance flag (redesign-2026 F4). Equivalent to ClusterID != "".
func (Role) Purpose ¶
Purpose is the one-line description of a canonical system role; empty otherwise.
func (Role) Validate ¶
Validate — multi-scope XOR formula + rules/permissions.
A role is valid when EITHER it carries authored Rules (the rules-model 2026 authority) OR a legacy compiled Permissions set (back-compat read of pre-rules roles).
When Rules is set (a rules-role) it is validated through Rules.Validate with the policy derived from the row (PolicyOfRole of IsSystem + OwnerModule) and the compiled Permissions projection is validated for the 4-seg grammar + cap ONLY — NOT the ≥1 lower bound (ValidateCompiled): a label-only role (all rules ARM_LABELS) compiles to an EMPTY permission set by design and must be accepted. The ≥1 floor is retained for the LEGACY permissions-only path (no Rules) so a degenerate legacy role with an empty set cannot exist. `modules` — набор модулей платформы, каким его знает вызывающий: правило роли называет модуль, а домен закрытого набора не объявляет (module_set.go).
func (Role) VerbNotes ¶
func (r Role) VerbNotes(lookup TypeVerbLookup) map[string]string
VerbNotes returns the per-verb clarifications for the effective preview. Only the editor `delete*` qualifier carries a note today.
func (Role) WithoutComputedState ¶
WithoutComputedState — копия роли без вычисленного состояния.
Приёмник — ЗНАЧЕНИЕ, и это не стиль: вызывающий переводит роль в ответ операции и продолжает пользоваться своей, поэтому проекция обязана быть неразрушающей.
TypeVerbs НЕ обнуляется намеренно: у него нулевое значение означает не «не вычислено», а ОТКАЗ проекции (см. комментарий поля), и путь мутации его заполняет сам — превью глаголов роль несёт и в ответе операции.
type RoleHealth ¶
type RoleHealth uint8
RoleHealth — состояние целости роли.
Нулевой вариант несёт ЕДИНСТВЕННЫЙ смысл: «этим ответом не вычислено». Он не является ни одним из трёх исходов и HealthOf его не возвращает никогда — иначе «посчитано, терять нечего» стало бы неотличимо от «не считали».
const ( // RoleHealthUnknown — не вычислено ЭТИМ ответом. НИКОГДА не «здорова». RoleHealthUnknown RoleHealth = iota // RoleHealthHealthy — каждый адресуемый сегмент имеет строку проекции. RoleHealthHealthy // RoleHealthDegraded — часть сегментов проекции не даёт, часть даёт. RoleHealthDegraded // RoleHealthEmpty — сегменты объявлены, проекции нет НИ ОДНОЙ. RoleHealthEmpty )
type RoleID ¶
type RoleID string
func SystemRoleID ¶
SystemRoleID — идентификатор системной роли по её ИМЕНИ.
Имя роли берётся ДОСЛОВНО: приведение имени к другому написанию перед хешированием (змеиное к камельному и обратно) даёт другой идентификатор и разрывает выданные права. Имя роли и имя ресурса — разные словари, и применитель их не сводит (приёмка §3.7).
type RoleIntegrity ¶
type RoleIntegrity struct {
Health RoleHealth
// Declared — сколько адресуемых сегментов объявляют правила роли.
Declared int
// Unresolved — сколько из них не дают ни одной строки проекции.
Unresolved int
}
RoleIntegrity — состояние целости и пара величин, из которых оно выведено.
Счётчики без состояния были бы двусмысленны: ноль есть И законная величина (роль без адресуемых сегментов), И признак «не считали». Разводит их RoleHealth: у вычисленной целости состояние непустое всегда.
func HealthOf ¶
func HealthOf(declared, unresolved int) RoleIntegrity
HealthOf выводит состояние из пары величин. ЕДИНСТВЕННОЕ место, где решается, какое состояние несёт роль.
Роль без адресуемых сегментов (подстановка в модуле и ресурсе — форма `*.*` администратора кластера, либо унаследованная роль без правил) читается ЗДОРОВОЙ, а не пустой: терять ей нечего, и тревога на ней была бы ложной. Прибор, чьи находки ложны, перестают читать — вместе с настоящими находками.
type RoleLifecycle ¶
type RoleLifecycle struct {
State RoleLifecycleState
// RetiredAt — момент снятия. Нулевое время у объявленной роли.
RetiredAt time.Time
// RetiredReason — причина снятия. Без неё «отобрали» неотличимо от
// «сломалось».
RetiredReason string
// RetiredBy — кто снял. Сегодня производитель один — путь старта, и он
// называет процессного актора; глагол применения придёт с `#1034` и назовёт
// проверенную личность вызывающего. Пустая строка у снятой роли означает
// «помечено до заведения колонки», а не «автора потеряли».
RetiredBy string
}
RoleLifecycle — состояние роли и обстоятельства её снятия.
Три сопутствующие величины непусты ТОЛЬКО у снятой роли, и это свойство СХЕМЫ, а не кода: `roles_live_matches_retired` делает состояние «снята и жива» неконструируемым.
func (RoleLifecycle) Withdrawn ¶
func (l RoleLifecycle) Withdrawn() bool
Withdrawn — снята ли роль. Отдельный вопрос от `State != Declared`: у невычисленного состояния ответ «нет», и он верен — утверждать снятие по молчанию нельзя.
type RoleLifecycleState ¶
type RoleLifecycleState uint8
RoleLifecycleState — объявлена роль манифестом либо снята.
Нулевой вариант несёт ЕДИНСТВЕННЫЙ смысл: «этим ответом не вычислено». Он не является ни одним из двух исходов, и путь чтения его не возвращает никогда — иначе «прочитано, роль объявлена» стало бы неотличимо от «не читали».
const ( // RoleLifecycleUnknown — не вычислено ЭТИМ ответом. НИКОГДА не «объявлена». RoleLifecycleUnknown RoleLifecycleState = iota // RoleLifecycleDeclared — строка жива: объявление за ней стоит. RoleLifecycleDeclared // RoleLifecycleWithdrawn — строка помечена снятой: объявления больше нет. RoleLifecycleWithdrawn )
func (RoleLifecycleState) String ¶
func (s RoleLifecycleState) String() string
String — состояние словом. Для журналов и текстов отказа, не для контракта.
type RoleName ¶
type RoleName string
func (RoleName) Validate ¶
Validate — форма имени роли БЕЗ различения яруса: годна любая из двух. Оставлена для вызывающих, которым ярус неизвестен; сама сущность судится `ValidateAtTier`, потому что ограничений в таблице ДВА и каждое условлено вычисляемым `is_system`.
func (RoleName) ValidateAtTier ¶
ValidateAtTier — форма имени роли ПО ЯРУСУ, зеркало двух ограничений таблицы.
Различать обязательно: в базе стоят ДВА условленных ограничения (`roles_custom_name_check` под `is_system OR …` и `roles_system_name_check` под `NOT is_system OR …`), и правило, не различающее ярусов, расходится с ними в обе стороны сразу — принимает точечное имя у пользовательской роли и отвергает его у системной. Отказ тогда приезжает от базы (SQLSTATE 23514) без имени поля и без координаты.
type RoleRetirement ¶
type RoleRetirement struct {
// Marked — строка роли помечена снятой. Ложно означает, что оператор пометки
// не нашёл своей строки: роль уже снята либо владелец не тот.
Marked bool
// ResettledVerbs — строк проекции глаголов переселено в ведомость и снято.
ResettledVerbs int
// ResettledRuleRefs — строк проекции сегментов переселено и снято.
ResettledRuleRefs int
// RemovedSelectors — строк проекции отбора снято. В ведомость они не
// переселяются: у строки селектора нет пары «тип + глагол», которой
// адресуются сироты.
RemovedSelectors int
// RemovedTargetMembers — строк материализованного состава цели снято.
RemovedTargetMembers int
}
RoleRetirement — перепись ОДНОГО отзыва роли: что унесено и что помечено.
Числа возвращаются, а не логируются внутри: вызывающий собирает из них перепись применения, а «снято» без чисел неотличимо от «прошло мимо».
type RoleRuleRef ¶
type RoleRuleRef struct {
Module string
Resource string
// Verb — пустая строка означает ЯКОРЬ (глаголы не сужены), а не отсутствие
// значения: см. абзац выше.
Verb string
}
RoleRuleRef — один ОБЪЯВЛЕННЫЙ сегмент правила роли: «эта роль называет вот этот (модуль, ресурс) и вот этот глагол».
Чем это НЕ является — и различие несущее ¶
Это НЕ `RoleVerb`. Та проекция отвечает на вопрос вердикта «разрешено ли действие» и содержит только то, что РЕЗОЛВИТСЯ: тип, которого не знает словарь модели, пар не даёт (`RoleVerbsFromSelectors`). Здесь наоборот — строка кладётся на КАЖДЫЙ объявленный сегмент, резолвится он или нет, потому что предмет этой таблицы есть ссылочная целостность: молчаливый пропуск и есть тот дефект, ради которого заводится ключ, и воспроизвести его в новом писателе значило бы завести ключ, которому нечего отвергать.
Пустой глагол — это ЯКОРЬ, а не «глагол не задан» ¶
Правило, не сузившее глаголы (`verbs: ["*"]`), даёт строку с пустым `Verb`, которая ложится в хранилище значением NULL. Ключ ресурса на ней проверяется, ключ глагола — пропускается `MATCH SIMPLE`, и это ПРАВИЛЬНО: ресурс уже проверен первым ключом. Ключей поэтому два, а не один: под одним составным ключом `MATCH SIMPLE` снял бы проверку целиком, и правило, называющее несуществующий ресурс, принималось бы успешно.
func RuleRefsOf ¶
func RuleRefsOf(rules Rules) []RoleRuleRef
RuleRefsOf — объявленные сегменты правил в форме строк проекции.
Источник — АВТОРСКОЕ правило, а не селекторы: селекторы уже прошли через словарь модели и потеряли то, что он не знает, — то есть ровно те сегменты, ради которых ключ и заводится. Подстановка `*` в ресурсе и модуле сегментов не даёт: она называет не имя, а «все», и адресовать ею строку каталога нечего (системная роль с `*.*` материализуется коротким замыканием администратора кластера).
func (RoleRuleRef) Dotted ¶
func (r RoleRuleRef) Dotted() string
Dotted — точечная форма имени типа (`vpc.network`), та самая, какой говорят `role_verb.object_type`, `role_rule_selectors.object_types` и колонка `catalog_resource.dotted` под своим CHECK.
ЕДИНСТВЕННОЕ место, где эта склейка пишется в Go: второе написание разошлось бы с первым молча, а соединение по разным словарям не совпадает никогда — и отличить это от «права нет» было бы нечем.
func (RoleRuleRef) IsAnchor ¶
func (r RoleRuleRef) IsAnchor() bool
IsAnchor — правило не сузило глаголы.
type RoleScopeGroup ¶
type RoleScopeGroup int8
RoleScopeGroup — server-computed scope tier of a role, surfaced to the UI in AssignableRole.scope_group so the picker groups without client-side logic Maps 1:1 to the proto ScopeGroup enum.
const ( RoleScopeGroupUnspecified RoleScopeGroup = 0 RoleScopeGroupSystem RoleScopeGroup = 1 RoleScopeGroupAccount RoleScopeGroup = 2 RoleScopeGroupProject RoleScopeGroup = 3 )
func ScopeGroupOf ¶
func ScopeGroupOf(r Role) RoleScopeGroup
ScopeGroupOf derives the scope tier from the role's scope columns. System roles are SYSTEM; otherwise the non-empty custom scope decides (account → ACCOUNT, project → PROJECT).
func (RoleScopeGroup) String ¶
func (g RoleScopeGroup) String() string
String — debug rendering (matches the proto enum names).
type RoleSegment ¶
type RoleSegment struct {
RoleID RoleID
ObjectType string
// Verb — пустая строка означает ЯКОРЬ (правило не сузило глаголы): годится
// любая строка проекции своего типа.
Verb string
}
RoleSegment — объявленный сегмент роли в форме, которой спрашивают проекцию.
Точечный тип приходит ОДНИМ выражением (RoleRuleRef.Dotted) — тем же, каким его строит переселение и каким его хранит каталог. Второе написание разошлось бы молча: соединение по разным словарям не совпадает никогда.
func SegmentsOf ¶
func SegmentsOf(id RoleID, rules Rules) []RoleSegment
SegmentsOf — объявленные сегменты роли в форме вопроса к проекции.
type RoleVerb ¶
RoleVerb — одна пара проекции «роль даёт этот глагол на этом типе».
Тип — тип объекта модели прав (`vpc_network`), не точечное имя ресурса: вердикт спрашивает именно им. Глагол — в канонической форме и БЕЗ приставки отношения: приставку знает компилятор модели, и дублировать её здесь значило бы завести второе место, где она может смениться.
type Rule ¶
type Rule struct {
Module string // exactly one module per rule
Resources []string
Verbs []string
ResourceNames []string
MatchLabels map[string]string
}
Rule — one authored grant over ONE module. The element lists are validated 1..16 each, the selector is resource_names XOR match_labels.
func (Rule) Fingerprint ¶
Fingerprint returns the content-hash of a rule (rule_fp). It is order-stable: the element lists and matchLabels keys are sorted before hashing, so the SAME rule authored in a different field order produces the SAME fp. The arm marker is folded in so an ARM_NAMES and an ARM_LABELS rule over the same modules/resources/verbs never collide.
The digest covers EVERY semantic field of the rule (module, resources, verbs, resource_names, match_labels) so a change to any of them — including a single label value or one verb — yields a different fp (the reorder/remove invariant rests on this).
func (Rule) Validate ¶
func (r Rule) Validate(policy RulePolicy, modules ModuleSet) error
Validate enforces the rule form. The wildcard relaxation is carried by RulePolicy — a single value derived from the row by PolicyOfRole — rather than by a boolean "system context": the module tier needs a THIRD policy, not a second boolean (see rule_policy.go). Errors carry the stable texts so the API contract is preserved.
`modules` — НАБОР МОДУЛЕЙ, каким его знает вызывающий (см. module_set.go). Он приходит параметром, потому что домен закрытого набора не объявляет: на пути запроса это ЖИВЫЕ строки каталога, у оснастки дерева — канон. Отсутствие набора отвергается самой строгой ветвью: «не знаю» не есть «можно».
type RuleLabelSelector ¶
type RuleLabelSelector struct {
RuleFP string
ObjectTypes []string
MatchLabels map[string]string
Verbs []string
}
RuleLabelSelector — the reconciler-facing projection of ONE ARM_LABELS rule: the rule_fp it is keyed by, the dotted object types it selects (cartesian modules×resources), the matchLabels equality selector, and the authored verbs (which drive the per-object tier + v_* relations the reconciler emits — the tier is NOT taken from the role's compiled permissions, since ARM_LABELS rules are excluded from CompileRules). It carries no pgx/grpc — pure domain.
type RuleLifecycle ¶
type RuleLifecycle uint8
RuleLifecycle — состояние ОДНОГО правила роли.
Нулевой вариант несёт ЕДИНСТВЕННЫЙ смысл: «этим ответом не вычислено». RuleStatesOf его не возвращает никогда — иначе «посчитано, терять нечего» стало бы неотличимо от «не считали».
const ( // RuleLifecycleUnknown — не вычислено ЭТИМ ответом. НИКОГДА не «действует». RuleLifecycleUnknown RuleLifecycle = iota // RuleLifecycleActive — ни один адресуемый сегмент правила не потерян. RuleLifecycleActive // RuleLifecycleWithdrawn — потерянные сегменты есть, и КАЖДЫЙ объяснён // ведомостью переселения: объявление сняла платформа. RuleLifecycleWithdrawn // RuleLifecycleUnresolved — потерянные сегменты есть, и ХОТЯ БЫ ОДИН не // объяснён: правило ссылается на то, чего сейчас нет. RuleLifecycleUnresolved )
type RulePolicy ¶
type RulePolicy struct {
// contains filtered or unexported fields
}
RulePolicy — политика послабления подстановки в правилах роли.
Почему политик ТРИ, а не две (задача продукта #1032) ¶
До неё политика была одна булева — «системный контекст». Признак `is_system` вычисляется ровно из непустого кластерного якоря и несёт ДВА смысла сразу: «арендатор эту роль не правит» и «этой роли можно подставлять звёздочку». Базовая роль модуля обязана быть системной в первом смысле — иначе её правит арендатор, — и вместе с ним получала второй: прямой путь к `*.*.*`. В диффе это выглядит обычной строкой роли, и ни один обзор её не поймает.
Политики различает ВЛАДЕЛЕЦ, а не ярус:
арендаторская якоря кластера нет module: "*" ✗ ресурс "*" ✗ модульная якорь есть, владелец назван module: "*" ✗ ресурс "*" ✓ только в своём модуле платформенная якорь есть, владельца нет module: "*" ✓ ресурс "*" ✓
Одно правило, а не три исключения: подстановка в роли с владельцем законна ровно в пределах её модуля. Отсюда обе строки механически — `module: "*"` не находится ни в одном модуле, поэтому отвергается всегда; ресурс `*` находится в модуле своего правила, поэтому законен ровно тогда, когда этот модуль и есть владелец.
Глагол `*` не затрагивается — решение, а не пропуск ¶
Он разрешён и в арендаторской роли, безусловно ([validateVerbs]): он не сегмент пространства имён, а «все действия названного типа». Сузить его здесь значило бы отобрать уже выданное под видом починки.
Носитель — ЗНАЧЕНИЕ, а не второй булев флаг ¶
Два булевых параметра дают четыре сочетания при трёх законных, и четвёртое («не системная, но с владельцем») пришлось бы отвергать четвёртым правилом. Политика передаётся одним значением закрытого перечня, выводимым из строки функцией ОДНОГО места — PolicyOfRole. Второе объявление «системная ли роль для целей подстановки» есть находка, и её ловит гейт дерева.
func PolicyOfRole ¶
func PolicyOfRole(isSystem bool, ownerModule string) RulePolicy
PolicyOfRole выводит политику ИЗ СТРОКИ — единственное место в дереве, где это делается.
`isSystem` берётся оттуда же, откуда его берёт база: из непустого кластерного якоря. `ownerModule` пуст у платформенной роли и непуст у роли, объявленной манифестом модуля.
Сочетание «не системная, но с владельцем» законным не является: владельца пишет только применитель ролей модуля, а он объявляет роль системной безусловно. Такой вход политику получает АРЕНДАТОРСКУЮ — самую строгую: послабление выдаётся по доказанному признаку, а не по частично совпавшему.
func TenantPolicy ¶
func TenantPolicy() RulePolicy
TenantPolicy — арендаторская политика в чистом виде.
Нужна вызывающему, у которого строки роли нет вовсе: загрузчик манифеста судит правила ДО того, как роль станет строкой, и обязан судить их самой строгой политикой. Порядок — часть контракта (сценарий IAM-OM-1-18): отказ приходит от загрузчика с координатой строки YAML, а не от применителя, у которого координаты нет.
func (RulePolicy) OwnerModule ¶
func (p RulePolicy) OwnerModule() string
OwnerModule — модуль-владелец роли; пусто у арендаторской и платформенной.
type RuleSelector ¶
type RuleSelector struct {
RuleFP string
Arm Arm
ObjectTypes []string
ResourceNames []string // ARM_NAMES only
MatchLabels map[string]string // ARM_LABELS only
Verbs []string
}
RuleSelector — the UNIFIED reconciler-facing projection of ONE materializing rule (RBAC explicit-model 2026). It carries the arm so the reconciler picks the match strategy: ARM_ANCHOR(all) → every object of ObjectTypes inside scope; ARM_NAMES → only ResourceNames; ARM_LABELS → labels @> MatchLabels. The per-object FGA tuples (v_* + tier) are derived from Verbs (the tier is NOT taken from the role's compiled permissions — ARM_LABELS/ARM_ANCHOR-materialized rules are not in CompileRules). Pure domain, no pgx/grpc.
type RuleState ¶
type RuleState struct {
// RuleIndex — индекс правила в `Role.Rules`. КЛЮЧ записи: порядок самих
// записей не значим, сверять надо по нему.
//
// Индекс законен как ключ потому, что `roles.rules` — массив JSONB под одним
// кодеком, и порядок сохраняется by construction: правило читается тем же
// индексом, каким записано. Действителен он в пределах ОДНОГО ответа:
// `Update` перестраивает массив, и мутацию индекс не переживает.
RuleIndex int
// State — состояние правила. У вычисленного оно непусто ВСЕГДА.
State RuleLifecycle
// Segments — сколько АДРЕСУЕМЫХ сегментов объявляет это правило.
// Ноль законен (подстановка в модуле или ресурсе сегментов не даёт).
//
// ЕДИНИЦА ДРУГАЯ, ЧЕМ У [RoleIntegrity.Declared], и потому имя другое:
// счётчик роли дедуплицирует сегмент по ВСЕЙ роли, здесь — только внутри
// правила. Равенство сумм поэтому НЕ ГАРАНТИРОВАНО и нарушается ровно
// тогда, когда один сегмент назван двумя правилами; на прочих ролях суммы
// совпадают, и это совпадение, а не инвариант.
Segments int
// Lost — сколько из объявленных сегментов этого правила не дают ни одной
// строки проекции, которую читает вердикт.
//
// ПРЕДИКАТ ТОТ ЖЕ, что у [RoleIntegrity.Unresolved]: ведомость здесь не
// спрашивается — её спрашивает [RuleState.Explained] рядом. Различает
// величины только единица дедупликации.
Lost int
// Explained — сколько из потерянных ОБЪЯСНЕНЫ ведомостью переселения.
// Необъяснённые выводятся вычитанием: третье поле, равное разности двух
// соседних, разошлось бы с ними при первой же правке.
Explained int
}
RuleState — состояние одного правила роли и три величины, из которых оно выведено.
Почему слово И счётчики, а не одно из двух ¶
Правило, у которого часть потерь объяснена, а часть нет, читается RuleLifecycleUnresolved — слово называет состояние, ТРЕБУЮЩЕЕ разбора. Схлопыванием это не является ровно потому, что рядом стоят обе величины: `Withdrawn` и `Unresolved` видны одновременно, и ни одна не потеряна.
Четвёртое значение («частично отозвано») рассмотрено и отвергнуто: у него нет ни своего действия у арендатора, ни своего производителя, при том что счётчики уже несут ровно ту же величину.
func RuleStatesOf ¶
func RuleStatesOf(rules Rules, unresolved []RoleSegment, withdrawn []WithdrawnGrant) []RuleState
RuleStatesOf — состояние каждого правила роли. ЕДИНСТВЕННОЕ место, где решается, какое состояние несёт правило.
Вход:
- rules — авторские правила роли;
- unresolved — сегменты роли, не давшие НИ ОДНОЙ строки проекции, которую читает вердикт. Источник состояния;
- withdrawn — ведомость переселения. Только ОБЪЯСНЯЕТ: судить состояние по ней значило бы читать роль, пострадавшую вторым путём, действующей.
Записей в результате ровно `len(rules)`, и `RuleIndex` каждой указывает на своё правило.
type Rules ¶
type Rules []Rule
Rules — a role's authored policy. Cardinality 1..64.
func DecodeRules ¶
DecodeRules decodes the roles.rules JSONB payload into domain Rules. An empty/nil payload yields nil (a legacy permissions-only role, rules='[]').
func OwnerRoleRules ¶
func OwnerRoleRules() Rules
OwnerRoleRules is the canonical authored policy of the `owner` system-role: `[{module:"*", resources:["*"], verbs:["*"]}]` (the `*.*.*` "selector all" shape). It MUST stay byte-for-byte semantically in lockstep with the migration 0035 seed (`rules` JSONB column). Exposed so the seed layer can derive the owner role's materializing selectors (role_rule_selectors) for the forward fast-path WITHOUT re-encoding the wildcard-expansion type set in SQL.
func (Rules) CoversType ¶
CoversType reports whether the role's authored rules grant verbs on the dotted `<module>.<resource>` type (redesign-2026 F9 gate 3 / IAM-1-24). A rule covers the type when its Module matches and its Resources list the resource (or "*"). Empty rules cover nothing.
func (Rules) HasAnchorRule ¶
HasAnchorRule reports whether any rule in the set uses the ARM_ANCHOR selector (selector=all: neither resource_names nor match_labels). An ARM_ANCHOR rule materializes over ALL instances under scope — on a GLOBAL (cluster) scope that is the cluster-wide per-object set Q-2/A-05 forbids for ordinary roles.
func (Rules) LabelSelectors ¶
func (rs Rules) LabelSelectors() []RuleLabelSelector
LabelSelectors projects a role's rules to the ARM_LABELS selectors the reconciler materializes. ARM_ANCHOR / ARM_NAMES rules are excluded (they emit at Create-time, not via the reconciler). The dotted types are `{module}.<resource>` over each label rule's resources (the module is scalar — no module unroll; wildcards are already rejected on a label rule by Rule.Validate, so no `*` reaches here in a well-formed role).
Retained as the ARM_LABELS-only view (back-compat). RBAC explicit-model 2026 routes the reconciler through MaterializingSelectors (ALL arms); use that for the unified materializer.
func (Rules) MaterializingSelectors ¶
func (rs Rules) MaterializingSelectors() []RuleSelector
MaterializingSelectors projects a role's rules to the UNIFIED selector set the reconciler materializes — ARM_ANCHOR(all) + ARM_NAMES + ARM_LABELS. Binding-time scope_grant emission is removed; the reconciler is the single path.
This is the SCOPE-AGNOSTIC (role-level) projection used to persist role_rule_selectors (the forward fast-path JOIN index): a wildcard `*.*` rule is EXPANDED to the full materializable type set so a freshly-registered object fast-path-matches an owner binding. The per-binding scope gate (MaterializingSelectorsInScope, consumed by the reconciler's LoadBinding) still prevents a GLOBAL/CLUSTER binding from per-object materializing, so expanding the role-level index is safe — the index over-includes, the binding scope narrows.
func (Rules) MaterializingSelectorsInScope ¶
func (rs Rules) MaterializingSelectorsInScope(scope Scope) []RuleSelector
MaterializingSelectorsInScope is the SCOPE-AWARE projection the reconciler uses to compute a binding's desired membership (LoadBinding). A wildcard `*.*` rule is expanded to the full materializable type set ONLY for a BOUNDED scope (ACCOUNT/PROJECT) — per-object owner content. For a GLOBAL/CLUSTER scope the wildcard yields NO ObjectTypes: cluster super-admin is the flat short-circuit, never per-object. A non-wildcard rule is scope-independent (its dotted types are explicit).
func (Rules) ScopeSelfVerbs ¶
ScopeSelfVerbs returns the UNION of authored verbs the role's rules grant on the binding's OWN scope resource-type — i.e. on the scope object itself (`account:<X>`/`project:<X>`/`cluster:<X>`). RBAC explicit-model 2026 P4 (D-7 / КФ-3 / C-01): a rules-role bound on a scope must materialize its tier (+ verb- bearing v_*) ON THE SCOPE ANCHOR ITSELF — the write-authz anchor / self-access that the removed binding-time scope_grant/anchor emit produced. The reconciler is now the SINGLE materialization path, so this projection feeds a scope-self desired member (reconcile.desiredRuleMembers), NOT a binding-time emit.
A rule contributes its verbs when EITHER its (module,resource) is the FULL `*.*` wildcard (the system superuser shape — migration 0031: admin/edit/view) OR its (module,resource) is exactly ("iam", scopeResource) — e.g. an `iam.account` rule on an account-scoped binding. scopeResource is the scope's resource type: "account"|"project". (cluster has no per-resource iam rule; only the `*.*` superuser shape grants cluster-self — handled by the wildcard branch.)
Returns nil when no rule applies to the scope self (a content-only role — e.g. `compute.instance` rules — grants nothing ON the account/project object, only on its content; the scope-self member is then absent, fail-closed).
func (Rules) Validate ¶
func (rs Rules) Validate(policy RulePolicy, modules ModuleSet) error
Validate validates the rule set: cardinality 1..64; each rule self-valid.
The policy is a single value derived from the row by PolicyOfRole; see rule_policy.go for why there are three of them and not two. `modules` — набор модулей вызывающего, см. module_set.go.
type SAOAuthClientID ¶
type SAOAuthClientID string
SAOAuthClientID — новый формат `soc<17-crockford>` (corelib `ids.NewID`, без подчёркивания). id существующих строк immutable (id = Hydra client id + JWK kid), поэтому валидатор принимает и legacy `soc_<17-crockford>`.
func (SAOAuthClientID) Validate ¶
func (id SAOAuthClientID) Validate() error
type Scope ¶
type Scope int8
Scope — anchor tier for an AccessBinding.
func DeriveFromResourceType ¶
DeriveFromResourceType — best-effort fallback for code paths that have resource_type but no explicit Scope (e.g. legacy callers that pre-date the W4 scope plumbing). Mirrors the BEFORE INSERT trigger of the schema; that the two agree is held by a probe, not by this sentence — см. §«Третье место» у [scopeVocabulary].
Вид вне словаря даёт ярус проекта — самый узкий из трёх, то есть ошибка умолчания не расширяет доступ.
func (Scope) ValidateAgainst ¶
ValidateAgainst checks that the Scope is consistent with the binding's (resource_type, resource_id). Returns ErrScopeMismatch if not.
CLUSTER ⇒ resource_type='cluster', resource_id='cluster_root' ACCOUNT ⇒ resource_type='account', resource_id starts with 'acc' PROJECT ⇒ resource_type='project', resource_id starts with 'prj'
Виды и их требования к идентификатору берутся из [scopeVocabulary]; своего перечисления здесь нет — оно было и разошлось бы с картой молча.
type ScopeAnchor ¶
ScopeAnchor — the resource the binding is scoped to (its containment anchor). Type is one of "project" | "account" | "cluster"; ID is the resource id.
type SelectorPruneOutcome ¶
type SelectorPruneOutcome uint8
SelectorPruneOutcome — что стало со строкой ОТБОРА, из которой вырезан тип.
Величины разделены намеренно: строка, потерявшая последний живой тип, снята целиком — правило перестало отбирать вовсе; строка, у которой живой тип остался, укорочена — правило сузилось. Для разбирающего последствия это события разного рода, и сложив их, мы потеряли бы ровно это различие.
const ( // SelectorPruneOutcomeUnknown — исход не прочитан ЭТИМ ответом. Никогда не // означает «исход неизвестен»: ведомость несёт его всегда, закрытым набором // на уровне схемы (`role_selector_prune_outcome_known`). SelectorPruneOutcomeUnknown SelectorPruneOutcome = iota // SelectorPruneOutcomeShortened — живой тип у строки остался, она укорочена: // правило продолжает отбирать, но уже меньше. SelectorPruneOutcomeShortened // SelectorPruneOutcomeDropped — живого типа не осталось, строка снята // целиком: правило не отбирает ничего. SelectorPruneOutcomeDropped )
type ServiceAccount ¶
type ServiceAccount struct {
ID ServiceAccountID
AccountID AccountID
Name SvcAccountName
Description Description
Enabled bool // default true
CreatedAt time.Time
// Labels — tenant-facing метки. Делают ServiceAccount label-selectable
// наравне с account/project (ARM_LABELS-грант на iam.serviceAccount → v_list
// по `labels @> matchLabels`; List фильтрует viewer ∪ v_list).
Labels Labels
}
ServiceAccount — Account-scoped (account_id FK ON DELETE RESTRICT).
Проектной области у служебной учётки нет: поле `project_id` и его колонка сняты — заполнить их было нечем (ни запроса, ни записи, ни выборки), а claim, который из них выводился, не читал никто. Понадобятся проектные служебные учётки — они заводятся отдельной подсистемой со своей приёмкой.
func (ServiceAccount) MayAuthenticate ¶
func (s ServiceAccount) MayAuthenticate() bool
MayAuthenticate reports whether this service account is allowed to obtain a token or a fresh credential. It is the single predicate every issuance path asks, the machine counterpart of InviteStatus.MayAuthenticate for users — so that no path can re-derive its own answer and quietly disagree with the rest.
It reads a field, which means it is only as truthful as the query that populated the struct: `enabled` is a bool, false in every zero value, so a read that does not select the column makes this method answer "no" for every account in existence. Callers therefore judge a row they actually read, and the reads that feed them are pinned by their own tests.
func (ServiceAccount) Validate ¶
func (s ServiceAccount) Validate() error
type ServiceAccountID ¶
type ServiceAccountID string
type ServiceAccountOAuthClient ¶
type ServiceAccountOAuthClient struct {
ID SAOAuthClientID
SvaID ServiceAccountID
OAuthClientID OAuthClientID
Description Description
CreatedByUserID UserID
CreatedAt time.Time
ExpiresAt *time.Time
LastUsedAt *time.Time
// PublicKeyPEM — SPKI-encoded ECDSA P-256 public key registered with
// Hydra as a JWK. Empty for legacy rows that pre-date the private_key_jwt
// mode (migrated with DEFAULT ”) AND for FEDERATED rows where the
// key material lives in the external IdP rather than kaname.
PublicKeyPEM string
// KeyAlgorithm — JOSE alg of the registered key. One of {"ES256",
// "RS256", "EdDSA"}. Empty for legacy rows; new private_key_jwt keys
// always set "ES256"; federated rows leave it empty.
KeyAlgorithm string
// TrustedSubjects — федеративный вид ключа. Непустой перечень означает, что
// удостоверение предъявляет ВНЕШНИЙ издатель по RFC 7521/7523: своего
// ключевого материала строка не несёт вовсе, а подпись проверяется ключом
// издателя из НАШЕГО перечня доверенных издателей (задача #1124), который
// читает проверяющий утверждения на пути запроса.
//
// Каждый элемент сужает, какая внешняя пара `(iss, sub)` вправе выступать
// за этот ключ. Пустой перечень — обычный вид с ключевым материалом.
TrustedSubjects []TrustedSubject
// DeclaredAudiences — сужение адресатов, ОБЪЯВЛЕННОЕ заказчиком при выдаче
// (`IssueSAKeyRequest.audience`, задача #1136). Create-only, как и всё
// остальное на этом ресурсе: глагола правки у ключа нет.
//
// Перечней в тракте выдачи ДВА, и этот — внутренний: он говорит, для чего
// заведён ЭТОТ ключ, и действует ВНУТРИ перечня, объявленного посадкой.
// Расширить внешнюю границу он не может ничем.
//
// Пустой перечень означает «сужения не объявлено», а не «любой адресат»:
// внешняя граница остаётся и требуется непустой стражем старта выдачи.
DeclaredAudiences []string
// CredentialKind — вид удостоверения. ЗАПИСЫВАЕТСЯ при вставке; читателем
// не вычисляется и из состава прочих полей не выводится.
CredentialKind CredentialKind
// SecretHash — sha256 по идентификатору строки И секретной части вместе,
// 32 байта. Непуст ТОЛЬКО у вида SECRET. Сам секрет не хранится нигде: он
// существует только в теле ответа, полученного вызывающим выдачи.
SecretHash []byte
// Name — человекочитаемое имя ключа, выставляется на Issue (create-only,
// immutable — ресурс несёт только Issue/List/Revoke). Пусто для legacy-строк.
Name OAuthClientName
// Labels — произвольные метки ключа, выставляются на Issue (create-only,
// immutable). Пусто для legacy-строк.
Labels Labels
}
ServiceAccountOAuthClient — Class A workload identity (Hydra static client).
private_key_jwt mode: kaname mints an ECDSA P-256 keypair per SA key, registers the public JWK with Hydra (`token_endpoint_auth_method = private_key_jwt`), and returns the private PEM to the caller exactly once. Hydra stores only the JWK; kaname keeps the SPKI public PEM (for rotation diagnostics) plus the algorithm. The legacy `client_secret_basic` flow is dropped: no secret ever exists.
1:1 SA→client.
func (ServiceAccountOAuthClient) Validate ¶
func (c ServiceAccountOAuthClient) Validate() error
type SessionRevocation ¶
type SessionRevocation struct {
TokenJTI string
RevokedAt time.Time
Reason string
UserID UserID
TTLExpiresAt time.Time
}
SessionRevocation — fast lookup table keyed by token_jti (PK). A cron cleanup deletes rows past ttl_expires_at (`DeleteExpired`, `pg/audit_session_revocation_repos.go`). Migration `0001_initial.sql`.
Номер миграции здесь был неверен — стояло 0013, а это про снятие перечня условий обхода. Правится вместе с тем же дефектом у соседа (`audit_outbox_entry.go`): радиус берётся по МЕХАНИЗМУ — «номер миграции, названный в шапке сущности», — а не по файлу, где дефект заметили.
func (SessionRevocation) Validate ¶
func (s SessionRevocation) Validate() error
type SigningAlgorithm ¶
type SigningAlgorithm string
SigningAlgorithm — алгоритм подписи токена. Словарь ЗАКРЫТ: значение вне него отвергается разбором конфигурации и стражем старта (приёмка F1 §3 строка 1, F1-03). Открытый словарь означал бы «принимаем любой» — тот же класс, что пустой перечень.
const ( SigningAlgRS256 SigningAlgorithm = tokenpolicy.AlgRS256 SigningAlgES256 SigningAlgorithm = tokenpolicy.AlgES256 SigningAlgEdDSA SigningAlgorithm = tokenpolicy.AlgEdDSA )
Закрытый словарь алгоритмов подписи. Значения ВЫВЕДЕНЫ из платформенного объявления (pkg/tokenpolicy), а не выписаны здесь второй раз: приёмная сторона живёт в другом сервисе, и две копии словаря разошлись бы молча — в сторону «принимаем больше», потому что расширять проще.
func ParseSigningAlgorithm ¶
func ParseSigningAlgorithm(raw string) (SigningAlgorithm, error)
ParseSigningAlgorithm разбирает значение конфигурации. Пустое значение — НЕ «умолчание»: у алгоритма подписи умолчания нет, потому что подпись умолчанием — решение, принятое за оператора.
func SigningAlgorithms ¶
func SigningAlgorithms() []SigningAlgorithm
SigningAlgorithms возвращает закрытый словарь целиком. Перечень ВЫВОДИТСЯ отсюда всеми, кому он нужен (страж старта, текст отказа, проверка ключа), а не выписывается по месту.
func (SigningAlgorithm) MinBits ¶
func (a SigningAlgorithm) MinBits() int
MinBits — нижний порог стойкости ключа, объявленный ЧИСЛОМ и ровно в одном месте дерева (F1-02). Для RSA это длина модуля; для кривых и Ed25519 длина задана самой кривой, поэтому порог у них выражен размером, который эта кривая даёт.
type SigningKeyRecord ¶
type SigningKeyRecord struct {
KID KeyID
Algorithm SigningAlgorithm
State SigningKeyState
PublicKeyPEM string
PrivateKeyWrapped []byte
CreatedAt time.Time
NotAfter time.Time
ActivatedAt *time.Time
RetiredAt *time.Time
RemovedAt *time.Time
CompromisedAt *time.Time
}
SigningKeyRecord — ХРАНИМАЯ форма ключа: несёт обёрнутую приватную половину.
Пара к PublishedKey. Разделение типов держит §6.10 КОМПИЛЯТОРОМ: форма «поле есть, но мы его не заполняем» держалась бы вниманием.
func (SigningKeyRecord) Published ¶
func (r SigningKeyRecord) Published() PublishedKey
Published — проекция строки в ПУБЛИКУЕМУЮ форму. Единственный переход между двумя типами, и он односторонний: обратного конструктора нет.
type SigningKeyState ¶
type SigningKeyState string
SigningKeyState — состояние ключа в ключнице.
Состояний ПЯТЬ, и «скомпрометирован» существует отдельно от «выведен» (приёмка §2.1): первое снимает ключ из набора немедленно, принимая отказ живых токенов, второе — нет. Глагол, делающий и то и другое, лишил бы второе решение его цены.
const ( // SigningKeyPublished — ключ в наборе, но ещё не подписывает. Этап // существует ровно ради порядка «в наборе → подписывает» (§6.1). SigningKeyPublished SigningKeyState = "PUBLISHED" // SigningKeyActive — ключ подписывает. Такой РОВНО ОДИН, и это держит // частичный уникальный индекс, а не проверка в коде (§6.2). SigningKeyActive SigningKeyState = "ACTIVE" // SigningKeyRetired — выведен из подписи, остаётся в наборе всю отсрочку: // подписанные им токены доживают свой срок (§6.4). SigningKeyRetired SigningKeyState = "RETIRED" // SigningKeyRemoved — отсрочка истекла, ключа в наборе нет. SigningKeyRemoved SigningKeyState = "REMOVED" // SigningKeyCompromised — покидает набор НЕМЕДЛЕННО; живые токены // отвергаются, и это принятая цена, а не дефект. SigningKeyCompromised SigningKeyState = "COMPROMISED" )
Состояния ключа.
func (SigningKeyState) CanActivate ¶
func (s SigningKeyState) CanActivate() bool
CanActivate отвечает, допускает ли машина состояний переход в ACTIVE.
Переход из REMOVED и COMPROMISED НЕ ВЫРАЖАЕТСЯ (F1-29): скомпрометированный ключ, вернувшийся в подпись, — не «редкий случай», а конструкция, которая получается сама, если её не запретить.
func (SigningKeyState) InKeySet ¶
func (s SigningKeyState) InKeySet() bool
InKeySet отвечает, попадает ли ключ в публикуемый набор.
Ответ следует СОСТОЯНИЮ, а не факту существования строки: набор, отдающий все строки подряд, отдал бы и снятый, и скомпрометированный.
type StructuralTuple ¶
type StructuralTuple struct {
// User — the SOURCE of the relation: the parent object ("account:<id>") for a
// parent-pointer, or the subject ("user:<id>") for the account owner.
User string
// Relation — the relation name on Object, named after the parent's type for a
// pointer ("account"/"project"/"cluster") or "owner".
Relation string
// Object — the object the relation is written on ("iam_access_binding:<id>").
Object string
}
StructuralTuple — one relation triple. Transport-neutral on purpose: the emitter converts it to its repo tuple type, and domain stays free of that type.
func AccountScopedStructuralFact ¶
func AccountScopedStructuralFact(accountID, objectType, objectID string) (StructuralTuple, bool)
AccountScopedStructuralFact is the one-line projection shared by the account-scoped iam types, whose cascade is `super_admin: admin from account`:
account:<accountID> → account → <objectType>:<objectID>
An empty accountID yields nothing: a row with no owning account (a system role) must not become reachable from any account's administrator.
type Subject ¶
type Subject struct {
Type SubjectType
ID SubjectID
}
Subject — one grantee of an AccessBinding (RBAC rules-model). A binding may carry 1..32 subjects; each yields an INDEPENDENT FGA tuple-set + emitted-tuple ledger lineage, so per-subject revoke/audit never touches another subject's tuples. A GROUP subject grants the role to every member (userset) — resolved to concrete principals by ExpandAccess and, for admin/editor-tier roles, requiring requireGrantAuthority on the scope (group-amplification guard).
func NormalizeSubjects ¶
func NormalizeSubjects(subjects []Subject, legacyType SubjectType, legacyID SubjectID) ([]Subject, error)
NormalizeSubjects resolves the canonical subjects[] set from the request input (two-way projection between the new subjects[] and the legacy single subject_type/subject_id):
- subjects[] is the canonical (preferred) input. When it is set, the legacy single pair (if also present) MUST equal subjects[0]; otherwise INVALID_ARGUMENT (a wire client cannot disagree with itself).
- When subjects[] is empty, the legacy single pair projects to a one-element subjects[] (legacy clients keep working).
- Empty subjects[] AND empty legacy single → INVALID_ARGUMENT ("Illegal argument subjects (must be 1..32)").
- More than 32 → INVALID_ARGUMENT (same text).
- A duplicate (type,id) → INVALID_ARGUMENT (the DB UNIQUE would also reject; fail sync with a clear message).
- Each subject is self-validated (closed type + non-empty id).
Pure domain — no DB / no transport. The use-case maps the returned error to the gRPC code (shared.MapValidationErr → INVALID_ARGUMENT).
type SubjectID ¶
type SubjectID string
SubjectID — opaque id (user, service_account, or group). The paired SubjectType defines the semantics.
type SubjectPrivilege ¶
type SubjectPrivilege struct {
BindingID AccessBindingID
RoleID RoleID
RoleName RoleName // resolved via JOIN; "" for a dangling/deleted role
ResourceType ResourceType
ResourceID string // opaque id (any prefix, cross-service OK)
Scope Scope // CLUSTER / ACCOUNT / PROJECT
Status AccessBindingStatus
CreatedAt time.Time
GrantedByUserID UserID
ExpiresAt *time.Time // nullable — TTL
// Derivation — DIRECT | GROUP. The zero value is treated as DIRECT by the
// transport projection (a row produced without an explicit derivation is a
// direct grant), so the proto enum never leaks UNSPECIFIED.
Derivation PrivilegeDerivation
// ViaGroupID — the group carrying the privilege when Derivation==GROUP; empty
// for a DIRECT grant. Without it a GROUP row is un-actionable: the binding's
// own subject is the group, so the administrator cannot tell which membership
// to remove.
ViaGroupID GroupID
}
SubjectPrivilege — enriched, public-safe projection of an AccessBinding for the subject-privileges view (RPC AccessBindingService.ListSubjectPrivileges).
It is an AccessBinding row JOINed with its Role so the human-readable RoleName is resolved server-side in ONE query (access_bindings ⋈ roles on role_id, same kaname schema, FK access_bindings_role_fk) — no per-row N+1 GetRole fan-out. A dangling role (deleted after a revoke) yields an empty RoleName; the consumer (UI) falls back to the raw RoleID (graceful).
Carries only tenant-facing, publicly-safe fields: id / role / scope / status / created_at / granted_by — никаких инфра-чувствительных данных и никаких condition/builtin_condition-internals (вне scope v1, security.md).
Derivation says HOW the subject holds the privilege: DIRECT (the binding names the subject itself) or GROUP (the binding names a group the subject belongs to, named by ViaGroupID). It is computed by the read query, not stored on the binding — a binding does not know which of its subjects' memberships a given reader is asking about.
type SubjectType ¶
type SubjectType string
SubjectType — enum: user|service_account|group.
const ( SubjectTypeUser SubjectType = "user" SubjectTypeServiceAccount SubjectType = "service_account" SubjectTypeGroup SubjectType = "group" )
SubjectType values.
func (SubjectType) Validate ¶
func (s SubjectType) Validate() error
Validate — SubjectType — enum check.
type SubordinateResource ¶
type SubordinateResource struct {
// Kind — двухчастный токен, которым подчинённый ресурс называется в видах
// учёта (`iam.user.credential` → `iam.credential`).
Kind LimitKind
// Parents — типы модели прав, от которых производен доступ. Их МОЖЕТ быть
// несколько: один класс сущности живёт в нескольких таблицах ровно тогда,
// когда видов родителя несколько.
Parents []LimitKind
// Tables — таблицы, чьи строки этот ресурс называет, в форме `схема.имя`.
// Это АНКЕР записи в дереве, а не справка.
Tables []string
// Why — почему своего типа модели прав нет. Запись без причины неотличима
// от записи без предмета.
Why string
}
SubordinateResource — ресурс, адресуемый арендатором и НЕ имеющий своего типа модели прав, потому что доступ к нему производен от РОДИТЕЛЯ.
Зачем понадобился второй источник имён ¶
Вид учёта обязан называть реальные типы модели прав — так гейт каталога и устроен, и обоснован он утверждением «модель прав знает КАЖДЫЙ адресуемый арендатором тип». На удостоверениях это утверждение ложно: они адресуются (`/iam/v1/users/{user_id}/tokens/{token_id}`), имеют свой идентификатор и свои глаголы, а типа модели прав у них нет — право на удостоверение вычисляется от человека (`token_issuer: subject`), и заводить объект, на который его можно было бы ВЫДАТЬ, модель намеренно избегает.
Почему это расширение, а не ослабление ¶
Множество допустимых имён остаётся закрытым и сверяемым с деревом. Разница с первой редакцией — в поле `Tables`: без него запись была бы САМОЗАЯВЛЕНИЕМ. У закрытой таблицы типов истинность анкерена гейтом дрейфа против канонической модели; здесь анкер — сами таблицы строк и стоящие на них триггеры списания (утверждения G5/G6 гейта каталога). Опечатка `iam.credentials` внутреннюю согласованность записи проходит и ловится ровно этим анкером.
func SubordinateResourceOf ¶
func SubordinateResourceOf(k LimitKind) (SubordinateResource, bool)
SubordinateResourceOf отдаёт запись подчинённого ресурса по его токену.
func SubordinateResources ¶
func SubordinateResources() []SubordinateResource
SubordinateResources отдаёт КОПИЮ перечня: перечень — предмет гейта, и вызывающий, способный его изменить, сделал бы гейт зависимым от порядка вызовов.
type SvcAccountName ¶
type SvcAccountName string
func (SvcAccountName) Validate ¶
func (n SvcAccountName) Validate() error
type TargetMember ¶
type TargetMember struct {
BindingID AccessBindingID
RoleID RoleID
RuleFP string
ObjectType string
ObjectID string
VerificationStatus VerificationStatus
}
TargetMember — one materialized member of a binding's target with its current verification status. object_type is a closed-table dotted key (e.g. "compute.instance"); object_id is an opaque cross-DB soft-ref.
RuleFP attributes the member to the (role) RULE that produced it: the content-hash of the ARM_LABELS rule (domain.Rule.Fingerprint) for a role.rules-driven member, or the sentinel "legacy-selector" for a legacy binding.selector member. Keying membership by rule_fp (not a positional index) lets a Role.Update that removes one rule eager-revoke ONLY that rule's members and lets the SAME object be a member under two different rules at different tiers (distinct rows).
type TrustedIssuer ¶
type TrustedIssuer struct {
// ClientID — НАША строка федеративного ключа, которую эта запись
// уполномочивает. Внешний субъект получает токен от её имени, никогда от
// своего.
ClientID string
// Issuer — идентификатор внешнего издателя, сверяемый с `iss` дословно.
Issuer string
// Subject — ТОЧНЫЙ субъект, сверяемый с `sub` дословно.
//
// Точный, а не образец: доверие образцу означало бы, что запись покрывает
// субъектов, которых называющий её не перечислял, — и узнать их состав
// нельзя ни по записи, ни по журналу.
Subject string
// PublicKeyPEM — открытый ключ ИЗДАТЕЛЯ в форме SPKI PEM.
PublicKeyPEM string
// Algorithm — зарегистрированный алгоритм издателя. Пустое значение
// означает «ключа нет», а НЕ «любой алгоритм».
Algorithm string
// ExpiresAt — момент истечения ДОВЕРИЯ в Unix-секундах; 0 означает
// «бессрочно».
ExpiresAt int64
}
TrustedIssuer — одна запись НАШЕГО перечня доверенных издателей.
Форма повторяет то, что прежде хранил у себя поставщик: издатель, ТОЧНЫЙ субъект, ключевой материал издателя и срок. Повторяет намеренно — перевод перечня к нам не должен был заодно менять то, о чём перечень.
func (TrustedIssuer) CanVouch ¶
func (ti TrustedIssuer) CanVouch() bool
CanVouch отвечает, способна ли запись вообще служить основанием доверия.
Тот же класс, что `AssertionClient.CanPresentAssertion`: пустой ключ и пустой алгоритм — это «ключа нет». Запись без ключа не отвергала бы ничего, а принимала бы всё, что называет её пару, — то есть доверие издателю выродилось бы в доверие строке таблицы.
type TrustedSubject ¶
type TrustedSubject struct {
Issuer string
SubjectPattern string
// PublicKeyPEM — открытый ключ ИЗДАТЕЛЯ (SPKI PEM). Тот, которым подписано
// внешнее утверждение; нашего ключевого материала федеративная строка не
// несёт вовсе.
PublicKeyPEM string
// KeyAlgorithm — зарегистрированный алгоритм издателя. Пустое значение
// означает «ключа нет», а НЕ «любой алгоритм».
KeyAlgorithm string
}
TrustedSubject — one (issuer, subject) tuple permitted to assert a federated ServiceAccountOAuthClient. `Issuer` MUST match the external OIDC `iss` claim verbatim; `SubjectPattern` is a LITERAL-anchored exact subject (`^<literal>$`, no regex metacharacters).
Точная форма субъекта требуется потому, что доверие выдаётся ПОИМЁННО: запись, покрывающая субъектов образцом, называет тех, кого выдававший не перечислял, и установить их состав нельзя ни по записи, ни по журналу.
Перечень — НАША таблица (задача #1124) ¶
Прежде решение о доверии принимал поставщик: запись жила у него, и там же лежал ключ издателя. Отсюда была выведена и прежняя редакция этого комментария — «служба прав вне пути запроса, поэтому образец было бы нечем применить». Сегодня служба прав НА пути запроса: перечень читает её проверка утверждения (`internal/clientassertion`, федеративная полоса). Точная форма осталась, но держит её теперь названный выше довод, а не чужая реализация.
func (TrustedSubject) LiteralSubject ¶
func (ts TrustedSubject) LiteralSubject() (string, bool)
LiteralSubject returns the exact subject enclosed by a valid literal-anchored pattern (`^<literal>$` → `<literal>`), and false when the pattern is not a literal-anchored subject (wildcard / unanchored / regex metacharacters).
func (TrustedSubject) Validate ¶
func (ts TrustedSubject) Validate() error
Validate — Issuer must be an https URL to a public host (anti-SSRF on the trust-config: no non-https / loopback / private / link-local host); SubjectPattern must be a literal-anchored exact subject. Length caps mirror the proto (≤512 each).
Ключевой материал издателя обязателен и проверяется на РАЗБИРАЕМОСТЬ здесь, а не при первом предъявлении: непригодный ключ, принятый на выдаче, даёт запись доверия, которая не примет никогда никого, — то есть возможность, объявленную и не работающую ни при каком входе. Отказ на выдаче виден тому, кто её заказал; отказ на предъявлении виден постороннему и неотличим для него от «доверия нет».
type TypeVerbLookup ¶
TypeVerbLookup — набор глаголов, объявленный типом (module, resource); ok=false, когда пара не резолвится ни в один известный тип.
Приходит ПАРАМЕТРОМ, а не импортом таблицы: владельцем таблицы остаётся authzmap, а домен — без внешних зависимостей (см. rule_verbs.go, «pure domain»).
func WithCommonFallback ¶
func WithCommonFallback(lookup TypeVerbLookup, common []string) TypeVerbLookup
WithCommonFallback оборачивает lookup так, что пара-ПОДСТАНОВКА получает словарь, ОБЩИЙ для всех ресурсов.
Это решение ВЫЗЫВАЮЩЕГО, не домена: правило-подстановка (`*.*` роли- суперпользователя) своего набора не имеет by construction — перечислить ресурсы подстановки домену нечем, каталог ему не принадлежит, — а пустое превью читалось бы как «роль ничего не даёт».
Запасной словарь даётся ПОДСТАНОВКЕ, а не всякому промаху (kacho#1814) ¶
Прежде его получала ЛЮБАЯ нерезолвящаяся пара, и это переворачивало смысл снятия ресурса: правило, называющее снятый `compute.disk`, разворачивалось в глаголы ВСЕЙ платформы, то есть после снятия превью показывало не «меньше», а БОЛЬШЕ. Роль обещала арендатору то, чего материализация не даёт, и обещала тем громче, чем уже правило.
Различает ФОРМА пары, а не исход резолва: подстановка называет «все» и потому законно берёт общий словарь; названный ресурс называет ОДИН тип, и если этого типа нет — давать нечего. Промах названной пары остаётся промахом (`ok=false`), и вызывающий разворачивает её ни во что.
type User ¶
type User struct {
ID UserID
AccountID AccountID
ExternalID ExternalSubject
Email Email
DisplayName DisplayName
InviteStatus InviteStatus
InvitedBy UserID // user.id of admin who invoked Invite; "" if self-signup
CreatedAt time.Time
// Labels — tenant-facing метки. Делают User label-selectable наравне с
// account/project: ARM_LABELS-грант на iam.user материализует v_list по
// `labels @> matchLabels`, а List фильтрует через viewer ∪ v_list.
Labels Labels
}
User — зеркало личности из внешнего провайдера. Одна личность — ОДНА строка, сколько бы аккаунтов её ни пригласило.
Принадлежность аккаунту здесь НЕ живёт: её выражает строка `kaname.memberships` (одна на пару «человек × аккаунт»), и членств у одного человека бывает несколько. Ключ идентичности — глобальный: `users_identity_email_uniq` и `users_identity_external_id_uniq` (`20260823050000_users_identity_uniqueness_goes_global.sql`, стадия S4-expand перехода IAM-ID-1). PENDING-строка держит external_id="" до первого входа.
Поле AccountID — ЛЕГАСИ-колонка перехода: она жива и `NOT NULL` до стадии S4, но «его аккаунт» из неё не читается — у человека их несколько.
type UserOAuthClient ¶
type UserOAuthClient struct {
ID UserOAuthClientID
UserID UserID
// OAuthClientID — идентификатор клиента у ВНЕШНЕГО поставщика.
//
// У строк нового выпуска ПУСТ и обязан быть пуст: выдача больше не заводит
// клиента у поставщика, а пустое значение здесь означает ровно это —
// регистрации нет. Непустое значение принадлежит строке прежнего выпуска и
// держит окно двух издателей: отчеканенные поставщиком токены таких строк
// действительны до своего истечения.
//
// На пути разрешения клиента эта колонка НЕ участвует (см.
// repo/kaname/pg.AssertionClientRepo).
OAuthClientID OAuthClientID
Description Description
CreatedByUserID UserID
CreatedAt time.Time
ExpiresAt *time.Time
LastUsedAt *time.Time
// PublicKeyPEM — SPKI-encoded ECDSA P-256 публичный ключ удостоверения.
// По нему проверяется подпись `client_assertion` на пути выдачи токена.
PublicKeyPEM string
// KeyAlgorithm — JOSE alg зарегистрированного ключа. Всегда "ES256" для новых
// токенов.
KeyAlgorithm string
// CredentialKind — вид удостоверения. ЗАПИСЫВАЕТСЯ при вставке; читателем
// не вычисляется и из состава прочих полей не выводится.
CredentialKind CredentialKind
// SecretHash — sha256 по идентификатору строки И секретной части вместе,
// 32 байта. Непуст ТОЛЬКО у вида SECRET. Сам секрет не хранится нигде: он
// существует только в теле ответа, полученного вызывающим выдачи.
SecretHash []byte
// Name — человекочитаемое имя токена, выставляется на Issue (create-only,
// immutable — ресурс несёт только Issue/List/Revoke). Пусто для legacy-строк.
Name OAuthClientName
// Labels — произвольные метки токена, выставляются на Issue (create-only,
// immutable). Пусто для legacy-строк.
Labels Labels
}
UserOAuthClient — персональный access-токен пользователя.
private_key_jwt: kaname генерирует пару ключей ECDSA P-256 на каждый токен, держит SPKI public PEM плюс алгоритм и возвращает приватный PEM вызывающему ровно один раз. Секрет не существует at-rest.
Клиентом это удостоверение называется по идентификатору ЭТОЙ строки: им подписывается `client_assertion`, и по нему же разрешает клиента наш реестр утверждений. Второго имени у него нет.
N:1 — у одного User может быть несколько токенов.
func (UserOAuthClient) Validate ¶
func (c UserOAuthClient) Validate() error
Validate — self-validating инвариант доменной сущности.
type UserOAuthClientID ¶
type UserOAuthClientID string
UserOAuthClientID — новый формат `uoc<17-crockford>` (corelib `ids.NewID`, без подчёркивания). id существующих строк immutable, поэтому валидатор принимает и legacy `uoc_<17-crockford>`.
func (UserOAuthClientID) Validate ¶
func (id UserOAuthClientID) Validate() error
type UserTokenRevocation ¶
type UserTokenRevocation struct {
UserID UserID
RevokeBefore time.Time
Reason string
RevokedBy UserID
}
UserTokenRevocation — per-user "revoke-all-before" cutoff (migration 0012). Backs admin ForceLogout + Revoke(revoke_all_user_tokens): any token whose originating session authenticated at or before RevokeBefore is denied at refresh. One row per user (PK user_id); the cutoff only ever advances (monotonic GREATEST upsert at the repo layer) so a re-auth past the cutoff is allowed again (no permanent lockout).
func (UserTokenRevocation) Validate ¶
func (u UserTokenRevocation) Validate() error
Validate — self-validating domain entity.
type VerificationStatus ¶
type VerificationStatus string
VerificationStatus — the observable per-member containment verdict.
const ( // VerificationPending — the object is NOT yet in resource_mirror (the grant // raced ahead of the owner's RegisterResource). No FGA tuple is emitted; the // reconciler verifies it when the mirror row arrives. VerificationPending VerificationStatus = "PENDING_VERIFICATION" // VerificationActive — the object is in resource_mirror AND under the // binding's scope-anchor. The per-object FGA tuple IS emitted. VerificationActive VerificationStatus = "ACTIVE" // VerificationRejected — the object is in resource_mirror but NOT under scope // (mirror.parent_* ⋢ scope). No tuple; an audit event is written (not silent). VerificationRejected VerificationStatus = "REJECTED" )
type WithdrawnGrant ¶
type WithdrawnGrant struct {
// ObjectType — точечный тип объекта, чью строку каталога сняли.
ObjectType string
// Verb — глагол. ПУСТАЯ строка есть ЯКОРЬ объявления правила, а не «любой
// глагол»: у объявления, не назвавшего глагол поимённо, его нет.
Verb string
// Source — из какой проекции переселена строка.
Source WithdrawnGrantSource
// Reason — причина снятия строки каталога, записанная применителем
// манифеста. Причина ПЛАТФОРМЫ, а не действие арендатора.
Reason string
// WithdrawnAt — момент переселения. Время ТРАНЗАКЦИИ применения: у всего,
// что отобрано одним применением, он совпадает дословно.
WithdrawnAt time.Time
// AppliedBy — АВТОР применения, снявшего эту строку (#2005): проверенная
// личность вызывающего на пути глагола либо названный процессный актор на
// пути старта. НЕ учётка, под которой исполнялась транзакция.
//
// Для разбора «почему у меня отобрали право» это второй по важности вопрос
// после «почему»: причина называет, ЧТО случилось с платформой, а автор — КТО
// это сделал.
//
// ПУСТАЯ строка означает «строка переселена ДО заведения колонки», а не
// «автора потеряли»: восстановить его у таких строк не из чего.
AppliedBy string
// Cause — ПОЧЕМУ строка переселена (#1913). Различие наблюдаемо, потому что
// от него зависит поведение: оживление роли снимает строки причины
// [WithdrawnGrantCauseRoleRetired] и не трогает строк причины
// [WithdrawnGrantCauseCatalogRetired].
//
// Отличается от [WithdrawnGrant.Source] предметом: тот называет, ИЗ КАКОЙ
// проекции строка переселена, этот — ПОЧЕМУ. Величины независимы: каждая
// причина встречается у обеих популяций.
Cause WithdrawnGrantCause
}
WithdrawnGrant — одна проекция правила роли, потерявшая референт при снятии строки каталога платформы и ПЕРЕСЕЛЁННАЯ в ведомость, а не отобранная молча.
Зачем этот тип, когда рядом уже стоят счётчики ¶
RoleIntegrity отвечает СКОЛЬКО сегментов перестало проецироваться и не отвечает, ЧТО именно и почему. Со стороны арендатора «право отобрали» без этого неотличимо от «права не было»: свою роль он не менял, а действовать она перестала.
Он ОБЪЯСНЯЕТ состояние и не определяет его ¶
Целость выводится из того, что читает вердикт, а не из ведомости: у роли, пострадавшей вторым путём (тип объявлен, а модель прав его не знает), переселения не было ВОВСЕ, и ведомость о ней пуста при нездоровом состоянии. Судить целость по ведомости значило бы читать такую роль здоровой — форма инцидента 513001.
type WithdrawnGrantCause ¶
type WithdrawnGrantCause uint8
WithdrawnGrantCause — почему проекция переселена в ведомость.
Нулевой вариант означает «этим ответом не вычислено» и НИКОГДА «причина неизвестна»: непонятая строка ведомости отвергается разбором, а не выдаётся за невычисленную.
const ( // WithdrawnGrantCauseUnknown — не вычислено ЭТИМ ответом. WithdrawnGrantCauseUnknown WithdrawnGrantCause = iota // WithdrawnGrantCauseCatalogRetired — снята строка КАТАЛОГА платформы, на // которую ссылалось правило. Роль при этом объявлена и жива. WithdrawnGrantCauseCatalogRetired // WithdrawnGrantCauseRoleRetired — снята САМА РОЛЬ. Возврат объявления // оживляет её и снимает эти строки. WithdrawnGrantCauseRoleRetired )
type WithdrawnGrantSource ¶
type WithdrawnGrantSource uint8
WithdrawnGrantSource — из какой проекции правила переселена строка ведомости.
Популяции разделены намеренно: «право отобрано» и «правило перестало резолвиться» — разные события для того, кто разбирает последствия, и сложив их, мы потеряли бы именно это различие.
const ( // WithdrawnGrantSourceUnknown — популяция не прочитана ЭТИМ ответом. Никогда // не означает «популяция неизвестна»: ведомость несёт её всегда, закрытым // набором на уровне схемы. WithdrawnGrantSourceUnknown WithdrawnGrantSource = iota // WithdrawnGrantSourceGrant — выдача глагола: право по паре действовало и // перестало. WithdrawnGrantSourceGrant // WithdrawnGrantSourceRuleRef — объявление правила: сегмент перестал // резолвиться в каталог. WithdrawnGrantSourceRuleRef )
Source Files
¶
- access_binding.go
- access_binding_scope.go
- access_binding_target.go
- account.go
- audit_outbox_entry.go
- client_assertion.go
- cluster.go
- cluster_admin_grant.go
- constants.go
- constants_extended.go
- credential_ceiling.go
- credential_kind.go
- derived_id.go
- federated_assertion.go
- feed_registry.go
- group.go
- ids_extended.go
- interactive_client.go
- limit.go
- limit_posture_stated.go
- membership.go
- module_set.go
- object_type_name.go
- principal_claims.go
- project.go
- recovery_completion.go
- retired_types.go
- role.go
- role_catalog.go
- role_cluster_admin.go
- role_computed_state.go
- role_definition_tier.go
- role_effective_verbs.go
- role_integrity.go
- role_lifecycle.go
- role_rule_state.go
- role_scope.go
- rule.go
- rule_codec.go
- rule_fingerprint.go
- rule_policy.go
- rule_verbs.go
- seeded_ids.go
- selector_feed.go
- service_account.go
- service_account_oauth_client.go
- session_revocation.go
- signing_key.go
- status.go
- structural_tuple.go
- subject.go
- subject_privilege.go
- target_membership.go
- types.go
- user.go
- user_oauth_client.go
- user_token_revocation.go