Documentation
¶
Overview ¶
Package subscriptionjournal — ОБЪЯВЛЕНИЕ ВЛАДЕЛЬЦА для общего сервера потока изменений (`corelib/subscription`).
Служба доступа — шестой владелец журнала и ПЕРВЫЙ, чьи предметы вместимы не проектом, а аккаунтом. Что из этого следует и чем закрыто — APPROVED-приёмка `docs/engineering/acceptance/access-resources-reach-a-narrowed-subscriber.md`; здесь она не пересказывается, потому что два места об одном предмете расходятся молча.
Что приносит владелец, и почему только значения ¶
Сервер берёт у владельца ТРИ объявления — где журнал лежит, каким каналом будит, как строка становится событием — и ничего больше. Курсор, горизонт устоявшегося, пределы, порядок отказов принадлежат серверу. Появись здесь возможность принести своё вместо любого из них, механизм перестал бы быть общим, оставшись общим по имени.
Состояние предмета этот журнал НЕ производит ¶
И причина называется СЛОВОМ, а не пустой нагрузкой: `NOT_PRODUCED` — первоклассное значение закрытого словаря контракта, машинно отличимое от сбоя сборки. Подписчик по нему знает, что делать: идти за предметом по `resource_id`, а на снятии — убрать его у себя.
Довод — §2.6 приёмки, и он не про объём работы: потребитель по событию ПЕРЕЧИТЫВАЕТ предмет, а не применяет нагрузку, а поверхность утечки для домена, чей предмет сами права, сужается до оболочки. Конверт состояния заводится своей задачей (`PRO-Robotech/kaname#72`).
Подтаблицы РОЛИ событий НЕ эмитят — решение #73, и оно ИЗМЕРЕНО ¶
Три подтаблицы состава (`group_members`, `memberships`, `access_binding_subjects`) эмитят правку владельца; три подтаблицы роли (`role_verb`, `role_rule_ref`, `role_rule_selectors`) не эмитят ничего. Это РЕШЕНИЕ, а не пропуск, и держится оно на двух вещах сразу.
ПЕРВОЕ: правка состава роли наблюдаема ЧЕРЕЗ СТРОКУ РОЛИ. Вызовов трёх `Replace*` в прод-коде девять, и каждый адъюдицирован. Создание роли — три замены в той же транзакции, что вставка строки. Правка — три замены под условием «маска назвала правила», а оператор под сторожем версии эти самые правила в строку и пишет. Приведение системной роли — замена под условием «поля изменились либо роль ожила», и строку роли пишет каждое из двух. Оставшиеся два вызова — досев старта, и он не меняет НИЧЕГО.
ВТОРОЕ: цена обратного исхода измерена, а не предположена. Подъём, в котором не изменилось ничего, кладёт в три подтаблицы 1558 строковых событий — полная замена проекции (661+661 глаголов, 92+92 сегментов) плюс 52 правки селекторов, — и все они про 48 ролей, то есть 32 дубля на роль. Сужение `OLD.* IS DISTINCT FROM NEW.*`, которым сужены семь основных таблиц, отсекает из них НОЛЬ: над двумя таблицами оператора `UPDATE` не существует вовсе (полная замена даёт DELETE+INSERT), а досев селекторов двигает `updated_at = now()`, поэтому предикат существа на нём истинен всегда. То есть сужение не «мало отсекает» — оно неприменимо by construction.
Числа — ориентир ревизии, и по памяти они здесь не переписываются: у них есть ПРОИЗВОДИТЕЛЬ — `internal/migrations/role_subtable_boot_volume_integration_test.go`. Он же держит пару, на которой решение стоит (правка строки роли событие даёт, холостой подъём — не даёт), а цену обратного исхода меряет инъекция рядом: те же триггеры, заведённые на три подтаблицы, дают 1558 событий журнала за один холостой подъём.
ОСТАТОК НАЗВАН, а не умолчан: путь последствий каталога отбирает у роли глагол либо сегмент, строки роли не трогая, — такой отзыв подписчику не виден. Заведён задачей `PRO-Robotech/kaname#76`; утверждение прибито §3 той же пробы и перевернётся вместе с починкой, а не разойдётся молча.
Index ¶
Constants ¶
const ( KindAccount = "account" KindProject = "project" KindUser = "iam_user" KindGroup = "iam_group" KindServiceAccount = "iam_service_account" KindRole = "iam_role" KindAccessBinding = "iam_access_binding" )
Виды предмета СЛОВОМ ЖУРНАЛА. Здесь они совпадают с типами объекта модели прав — не по совпадению, а по требованию механизма: вид, у которого нет типа модели, недоставляем, потому что вопрос о видимости его строки задать нечем.
Единого словаря типов у службы нет — каждый use-case держит своё локальное значение, — поэтому перечень объявлен ЗДЕСЬ и СВЯЗАН С ДЕРЕВОМ пробой: каждый вид обязан быть живым типом канонической модели, а перечень — совпадать со словарём, закрытым ограничением миграции. Объявление без такой связи устарело бы молча.
const Table = "kaname.resource_journal"
Table — журнал службы. Имя НЕ на `_outbox`: у службы шесть очередей с таким окончанием, и ни одна лентой изменений не является.
Variables ¶
This section is empty.
Functions ¶
func ProjectGate ¶
func ProjectGate() (subscription.ProjectGate, error)
ProjectGate — страж оси `project_id`.
Форма отсутствия приносится ОТ ПРОИЗВОДИТЕЛЯ, а не сочиняется здесь: отказ доступа обязан быть неотличим от «такого проекта нет», иначе различимый текст превращает подписку в способ узнать существование чужого проекта.
Types ¶
type Door ¶
type Door interface {
BatchCheckWithContext(ctx context.Context, subject, relation string,
objects []string, condCtx map[string]any) (allowed []bool, err error)
}
Door — дверь решения о доступе. Узкий порт, и он ТОТ ЖЕ, что у списков: второй источник ответа на один вопрос об одном объекте здесь уже однажды разошёлся, и заводить его снова нельзя.
type NarrowClient ¶
type NarrowClient struct {
// contains filtered or unexported fields
}
NarrowClient — реализация порта сужателя фундамента.
Сужатель строится из этого клиента (`listnarrow.New`), поэтому владелец отвечает на КАЖДЫЙ пообъектный вопрос, оставаясь при штатном типе сужателя.
func NewNarrowClient ¶
func NewNarrowClient(door Door, removals RemovalScopes) *NarrowClient
NewNarrowClient — сборка из композиционного корня.
func (*NarrowClient) BatchCheck ¶
func (c *NarrowClient) BatchCheck(ctx context.Context, checks []listnarrow.Check) ([]bool, error)
BatchCheck — вердикты В ПОРЯДКЕ ВОПРОСОВ и ТОЙ ЖЕ ДЛИНЫ.
Контракт длины и порядка держится позиционной записью: ответ пишется в СВОЙ индекс и никогда не дописывается. Переставленный вердикт отфильтровал бы поток чужим ответом, и заметить это вызывающий не может.
Отказ двери прекращает партию целиком: «не смог спросить» и «доступа нет» — разные миры, и представление первого на успешном пути сделало бы недоступность базы неотличимой от законного отказа.
type PoolRemovalScopes ¶
type PoolRemovalScopes struct {
// contains filtered or unexported fields
}
PoolRemovalScopes — чтение журнала из пула.
Пул, а не выделенное соединение подписки: то занято `LISTEN` всё время жизни потока, и читать им значило бы делить одно соединение между ожиданием пробуждения и вопросом.
func NewPoolRemovalScopes ¶
func NewPoolRemovalScopes(pool *pgxpool.Pool) *PoolRemovalScopes
NewPoolRemovalScopes — сборка из композиционного корня.
func (*PoolRemovalScopes) CapturedScopes ¶
func (a *PoolRemovalScopes) CapturedScopes( ctx context.Context, kind string, ids []string, ) (map[string][]Scope, error)
CapturedScopes — области ТОЛЬКО тех предметов, чья свежая строка есть снятие.
Предмет, у которого свежая строка не снятие, в ответе отсутствует: наличие ключа и есть признак «снят». Пустая карта — законный ответ, а не сбой.
type RemovalScopes ¶
type RemovalScopes interface {
// CapturedScopes возвращает области ТОЛЬКО для снятых предметов. Предмет, у
// которого снятия нет, в ответе отсутствует — и это несущее: наличие ключа
// и есть признак «строка журнала по нему снятие».
CapturedScopes(ctx context.Context, kind string, ids []string) (map[string][]Scope, error)
}
RemovalScopes — журнал в той части, которая нужна сужателю: какие из названных предметов СНЯТЫ и какие области у них захвачены.
Порт объявлен здесь, а не импортирован, чтобы клиент не зависел от того, кто ему отвечает.