authzformbench

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 13, 2026 License: AGPL-3.0 Imports: 25 Imported by: 0

Documentation

Overview

Package authzformbench измеряет стоимость РЕЛЯЦИОННОЙ формы выдачи прав — работу записи, работу чтения и объём — как КРИВУЮ по N, а не как одно число.

Это измерительный прибор, а не часть продукта. Ничто под services/, pkg/, gateway/ или proto/ его не импортирует, и он ничего туда не пишет: каноническая модель `proto/kaname/cloud/iam/v1/fga_model.fga` только ЧИТАЕТСЯ.

Что здесь было и чего больше нет (S6)

Пакет заводился как СРАВНЕНИЕ ШЕСТИ форм одной и той же выдачи: пяти способов разложить её в кортежи внешнего движка отношений и шестого — реляционного, где вердикт вычисляется запросом к БД. Движок снят из продукта целиком, и вместе с ним снята половина прибора: его транспорт, его посадка, преобразования канонической модели под формы C и D, вопросник согласия сторон и все пять форм хранения В НЁМ. Мерить их больше нечем — они были формами хранения в движке, и вне его не существуют.

Осталась форма E, и осталась она РАБОЧЕЙ. Сравнивать её теперь не с чем, и это сказано прямо во всех местах, где иначе читалось бы иначе: в отчёте, в правиле отнесения и в шапках проб. Что при этом НЕ пропало:

  • абсолютная стоимость формы — сколько стоит выдать, отозвать, переразметить, спросить один объект и целую страницу, и сколько это занимает строк;
  • НАКЛОН по N. Он и был главным утверждением замера: цена выдачи у формы E постоянна (S + 2) при том, что материализованный состав рос как N·M·S. Постоянство — результат, а не отказ прибора, и без второй стороны наклон остался единственным содержательным утверждением;
  • окно неверного ответа на пути меток (`labelcost.go`) — сколько времени доступ остаётся у того, кто его уже потерял.

Предмет

Правило роли сужается селектором меток. Материализующая форма разворачивала его в кортежи: N объектов × M глаголов × S субъектов — три линейных множителя, без показателя степени. При N=1000, M=5, S=10 это 50 000 строк из одной строки роли. Реляционная форма не разворачивает ничего: она хранит привязку, её субъектов и её селектор (S + 2) и вычисляет вердикт запросом на пути чтения — который горячий: страница договора до 1000 объектов.

Что измеряется и в каких единицах

Запись, по операциям, — время стены И число SQL-стейтментов (величина, у которой есть объявленная ДО прогона арифметика, см. `ExpectedStatements`):

W1 grant       материализовать привязку над N объектами
W2 revoke      снять ОДНОГО субъекта
W3 relabel-1   один объект входит в набор
W4 relabel-K   K объектов входят в набор
T-inline-*     выдача и отзыв В ОДНОЙ транзакции с предметом выдачи

Чтение, по операциям, — время стены:

R1 check       один объект, один глагол, один субъект
R2 page-50     одна часть страницы
R3 page-1000   страница договора (pkg/validate.MaxPageSize)
C-cascade      вопрос каскадного принципала — того, кто чинит аварию

Объём: строки выдачи и логические байты, которые Postgres относит к строкам хранилища. Накладные расходы индекса табличные, а не построковые, поэтому печатаются отдельно и никакой строке не приписываются.

Исходов ТРИ, и третий не вычитается ни в чью пользу

Измерено · не выполнилось · неизмеряемое, названное текстом. «Не выполнилось» — стек не поднялся, операция вышла за срок — НИКОГДА не сворачивается в «быстро» и не опускается: оно доносится до отчёта своей строкой с причиной.

Категорий было четыре. «Отказ движка» и «неприменимо by construction» сняты вместе со своими производителями — подробности в комментарии к `Outcome`. Вторая из них была самым содержательным результатом таблицы: у движка не могло быть общей транзакции с БД предмета выдачи. У формы E эта операция выразима, поэтому она теперь ИЗМЕРЯЕТСЯ.

Прибор обязан быть показан РАЗЛИЧАЮЩИМ свой вход

Прежде это доказывалось двумя способами сразу: удвоением N на форме, чья цена и есть N·M·S, и неравенством объёмов между формами. Оба способа ушли с движком, и требование от этого стало СТРОЖЕ, а не слабее: у формы E цена выдачи постоянна по N, поэтому различающую силу приходится показывать на величине, которая обязана расти, — структурной части (строка зеркала на объект). Она объявлена ДО прогона (`ExpectedStructuralRows`) и сверяется с измеренной; там же сверяется постоянство выдачи и поштучная арифметика стейтментов. Доказательство — `TestHarnessSeesTheInputOfFormE`.

Живость канала длительностей держат ТРИ пробы, а не одна, и делят её так, чтобы ни одна не спрашивала машину о её загрузке: годность пришедшей ячейки — `cellDurationChannelIsLive` (счётные и структурные свойства, зовётся из харнесса); неспособность ПРИБОРА отдать константу — `TestTimerAnswersTheWorkItWasGiven` (разница входов создаётся ожиданием, допуск назван числом); неспособность ВЫЧИСЛИТЕЛЯ схлопнуть разницу — `TestDurationChannelPropagatesADifference`. Прежде первую и вторую нёс один блок харнесса, требовавший, чтобы два замера различались, — и он ронял исправный стенд, когда часы не различали повторы (#1516).

Провенанс

Отчёт называет образ Postgres, машину, ревизию дерева, дату и отпечаток канонической модели. Замер без них — не свидетельство, потому что его нельзя повторить. Модель читается и после снятия движка: план вердикта формы E компилируется из неё продуктовым `services/iam/internal/authzplan`, поэтому «на какой модели снят замер» остаётся осмысленным вопросом.

Два места снятия, и путать их нельзя

Прибор держит СВОЙ Postgres (`stack.go`, один контейнер на процесс, изоляция схемой — доказательство в `isolation_test.go`) и меряет на нём воспроизведение формы E. Продуктовую форму E — запрос по настоящим таблицам iam — меряет прогон Ф5, и живёт он не здесь, а рядом с ней: правило видимости Go разрешает импортировать `services/iam/internal/...` только из дерева `services/iam/`. Прогон, положенный рядом с прибором, мерил бы воспроизведение продукта, а не продукт. Поэтому каждая ячейка несёт признак `Place`.

Index

Constants

View Source
const (
	MarkerComposition = "отпечаток прибора: состав     "
	MarkerContent     = "отпечаток прибора: содержимое "
	MarkerPredicate   = "отпечаток прибора: предикат   "
	MarkerFileList    = "" /* 158-byte string literal not displayed */
	MarkerFile        = "    "
)

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

View Source
const (
	VerbPrefix      = authzplan.VerbPrefix
	MaxPointerDepth = authzplan.MaxPointerDepth
)
View Source
const (
	TermDirect   = authzplan.TermDirect
	TermComputed = authzplan.TermComputed
	TermTTU      = authzplan.TermTTU

	AtomBinding = authzplan.AtomBinding
	AtomFact    = authzplan.AtomFact
)

Константы видов терма и атома — того же перечисления, что у продукта.

View Source
const AttributionRule = `` /* 6633-byte string literal not displayed */

AttributionRule — правило отнесения стоимости, объявленное ДО прогона.

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

View Source
const BenchType = "vpc_network"

BenchType — тип объекта, на котором снимается замер.

Переехал сюда из снятого файла преобразований модели: там он называл тип, чьи глаголы переписывались под формы C и D, здесь — просто тип объектов фикстуры.

View Source
const CascadeChain = "leaf → project → account → cluster"

CascadeChain — объявленная цепь обхода каскада. Глубина ОГРАНИЧЕНА и это факт дерева, а не допущение: три верхних уровня доступа разрешаются каскадом намеренно (аварийный путь не должен зависеть от состояния конвейера), а вложенности групп в продукте нет.

View Source
const CascadeDepth = 3

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

View Source
const FingerprintPredicate = `все .go каталога ` + benchDir +
	` — включая _test.go: харнесс прогона (сценарий, размер страницы, повторы) живёт в них,` +
	` и замер, снятый другим харнессом, — другой замер`

FingerprintPredicate — предикат печатается В ШАПКУ рядом с отпечатком, чтобы читатель мог его ПОВТОРИТЬ и оспорить, а не поверить шестнадцати знакам хэша.

Отличие от предиката `scalegrid` названо прямо: там под отпечаток идут только НЕ-тестовые файлы, здесь — ВСЕ. Причина в устройстве прибора: харнесс, задающий сценарий, размер страницы и число повторов, живёт именно в `_test.go` (`run_test.go`, `harness_test.go`), и замер, снятый другим харнессом, — другой замер. Исключив их, отпечаток молчал бы на смене того, ЧТО мерили.

View Source
const QueuePredicate = `grep -rn 'KANAME_RECONCILE_DRAIN_INTERVAL_MS|PollFallback|pg_notify' ` +
	`services/iam/cmd/kaname/serve.go services/iam/cmd/kaname/fga_outbox_drainer.go ` +
	`services/iam/internal/repo/kaname/pg/reconcile_notify.go`

QueuePredicate — предикат, которым читается неизмеренная часть окна.

Предикатом, а не числом: число из памяти устаревает молча, команда — нет.

И НИКАКОГО «пола» из этих величин не строится. Обе очереди продукта пробуждаются УВЕДОМЛЕНИЕМ, а перечисленные периоды — запасной путь на пропущенное уведомление. Объявить период опроса нижней границей окна значило бы назвать числом то, что на штатном пути не выполняется, — придуманная величина в отчёте, чей предмет как раз в том, чтобы величины были измеренными.

Variables

View Source
var (
	ParseModel            = authzplan.ParseModel
	ResolveCanonicalModel = authzplan.ResolveCanonicalModel
	IsVerb                = authzplan.IsVerb
)
View Source
var AllForms = []Form{FormE}

AllForms — перечень измеряемых форм в порядке отчёта.

View Source
var ErrPlanNotExpressible = fmt.Errorf("отношение не выражается в схеме формы E целиком")

ErrPlanNotExpressible — у отношения нет выражения в форме E целиком.

Это НЕ отказ в доступе и не сбой: третий исход, который обязан быть отличим от обоих. Свернуть его в «запрещено» значило бы напечатать совпадение там, где вопрос не задавался, — и именно так невыразимая часть модели стала бы невидимой.

Functions

func CloseSharedStack

func CloseSharedStack()

CloseSharedStack гасит контейнер. Безопасна, когда ничего не поднималось.

func ContentOf

func ContentOf(root, rel string) string

ContentOf — отпечаток содержимого ОДНОГО файла; гейт называет им виновника.

func DefaultVerbs

func DefaultVerbs() []string

DefaultVerbs is the verb set 22 of the model's 24 verb-bearing types declare.

func ExpectedGrantTuples

func ExpectedGrantTuples(f Form, sc Scenario) int

ExpectedGrantTuples — арифметика, объявленная ДО прогона, чтобы измеренное число строк сверялось с ней, а не принималось на веру.

func ExpectedStatements

func ExpectedStatements(f Form, op Op) int

ExpectedStatements — объявленная ДО прогона арифметика колонки `StmtSQL` формы E, по операциям.

Она нужна не для красоты отчёта: без неё измеряемое проверялось бы только по объёму, а объём у формы E постоянен — и форма, которая ВДОБАВОК к выдаче переразмечает весь набор, прошла бы все утверждения о строках, потому что правка метки строк не добавляет. Дефект найден инъекцией собственной пробы, а не рассуждением: инъекция «форма E разворачивает набор» пережила проверку объёма и упала только на этой величине.

-1 — «не объявлена». Прежде так отвечали пять форм движка: стейтменты у них порождал он сам, и их число было свойством его реализации, а не нашим объявлением. Сегодня так отвечает только объём — у него нет колонки стейтментов, потому что это не операция замера времени.

func ExpectedStructuralRows

func ExpectedStructuralRows(f Form, sc Scenario) int

ExpectedStructuralRows — объявленная арифметика структурной части.

Названа отдельно и до прогона, потому что у формы E ЭТО и есть величина, обязанная расти с N: цена самой выдачи у неё постоянна (s + 2), и постоянство — законный результат, а не отказ прибора. Форма, у которой ни одна величина не отвечает на удвоение входа, оставляет предпосылку «прибор различает» недоказанной, поэтому величина, которая обязана вырасти, названа заранее и сверяется с измеренной. С уходом второй стороны эта предпосылка стала ВАЖНЕЕ, а не менее важной: раньше различающую силу прибора можно было показать разницей между формами, теперь показать её нечем, кроме отклика одной формы на вход. Слагаемое 5 (а не 2) — чужой арендатор: второй аккаунт, его проект и один помеченный объект в нём. Оно названо ЗДЕСЬ, потому что эта арифметика — единственное место, где заявленный размер фикстуры сверяется с измеренным: пробы объёма упадут, если фикстура вырастет иначе, чем объявлено.

func FingerprintHeader

func FingerprintHeader() string

FingerprintHeader — то, что печатает КАЖДЫЙ писатель отчёта.

Корень спрашивается у git, а не собирается из `..`: число шагов вверх зависит от того, откуда прогон запущен, и переезд вызывающего молча увёл бы отпечаток считать чужой каталог.

Отказ печатается СТРОКОЙ В ОТЧЁТ, а не глотается: отчёт без отпечатка обязан говорить, почему его нет, — иначе гейт увидит «шапки нет» и не отличит «прибор не смог» от «отчёт древний».

func IsRevocation

func IsRevocation(op LabelOp) bool

IsRevocation — операция ли это отзыва. Перечень один, и он здесь.

func RepoRoot

func RepoRoot() (string, error)

RepoRoot — корень дерева, спрошенный у git.

Через `pkg/gitenv`, а не прямым `exec.Command("git", …)`: унаследованные `GIT_DIR`/`GIT_INDEX_FILE` увели бы команду в чужой репозиторий — это находка отдельного гейта дерева.

func Report

func Report(w io.Writer, prov Provenance, notes map[Form]string, cfg Config, cells []Cell)

Report renders the matrix as a CURVE — one block per operation, forms down, N across — because a table keyed by form alone would hide the only thing that matters: whether the lines cross, and where.

Every cell prints its outcome. A "not-run" prints as such WITH its reason and is never rendered as a blank or a dash that a reader could mistake for a zero.

func ReportLabelPath

func ReportLabelPath(w io.Writer, in LabelReportInput, cells []LabelCell)

ReportLabelPath печатает отчёт пути меток.

Порядок разделов: правило отнесения → провенанс → величины → окна → окна отзыва отдельно → неизмеренное. Правило первым потому, что число, прочитанное раньше правила, читатель отнесёт по своему.

func Verb

func Verb(v string) string

Verb — модельное имя глагола (с приставкой) по канонической форме.

Types

type Atom

type Atom = authzplan.Atom

type AtomKind

type AtomKind = authzplan.AtomKind

type Cell

type Cell struct {
	Form    Form
	N       int
	Op      Op
	Outcome Outcome
	Reason  string
	Place   string // откуда снята ячейка — отчёт из двух мест без этого признака выдаёт за один прогон два

	Repeats            int
	P50, P95, Min, Max float64 // milliseconds; zero when Outcome != Measured

	// Колонка обращений была РАСЩЕПЛЕНА на две самостоятельные, пока сторон было
	// две: у движка «обращение» — HTTP-вызов, за которым стоит неизвестное число
	// запросов к его Postgres, у формы E — SQL-стейтмент. Складывать их было
	// нельзя. Осталась одна величина; вторая снята, а не обнулена.
	StmtSQL  int    // SQL-стейтменты
	StmtNote string // непусто ⇒ StmtSQL НЕ измерен, здесь причина; печатается вместо величины

	Parts  int // на сколько частей разложена страница (у формы E — одна)
	Tuples int // строк намерения, которых операция коснулась

	GrantTotal      int64 // volume only: grant rows in the store
	GrantBytes      int64 // volume only: logical row bytes of ALL its rows (см. Volume)
	StructuralRows  int64 // volume only: то, что есть у каждой формы одинаково
	StructuralBytes int64
	TableBytes      int64 // volume only: whole relations, incl. indexes — NOT per-shape
}

Cell is one (form, N, op) result.

func (*Cell) Stats

func (c *Cell) Stats(ms []float64)

Stats fills the percentile fields from raw millisecond samples.

type Census

type Census = authzplan.Census

type Config

type Config struct {
	Ns           []int // the curve — at least four orders, or the slope is unknown
	Subjects     int   // S
	Verbs        []string
	Role         string
	RelabelK     int // K for W4
	PageSize     int // R3 page size (the contract page is 1000)
	Partition    int // objects per BatchCheck request
	Parallelism  int // partitions in flight
	WriteRepeats int // measured repeats for write ops (warm-up is extra)
	ReadRepeats  int // measured samples for read ops (warm-up is extra)
	Forms        []Form
}

Config governs one run.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig mirrors the production numbers wherever one exists: the partition and parallelism are authzfilter's (50 / 8) and the page is the contract's max (pkg/validate.MaxPageSize = 1000). Where no production number exists — repeats — the value is the minimum the methodology demands, not a comfortable one.

type Counters

type Counters struct {
	StmtSQL int // SQL-стейтменты, снятые на Postgres
}

Counters — обращения, снятые на одной измеряемой операции.

Колонок было ДВЕ, и это было содержательно, пока сравнивались две стороны: у движка отношений «обращение» было HTTP-вызовом, за которым стояло неизвестное число запросов к его Postgres, у формы E — SQL-стейтмент. Складывать их было нельзя ни в одной ячейке.

Второй стороны больше нет (S6), и колонка обращений к движку СНЯТА, а не оставлена нулём: величина, у которой не осталось производителя, печатается нулём, неотличимым от измеренного, — ровно тот класс, против которого заведён `ProducerStatus` ниже. Осталась одна величина, и у неё есть имя.

type DirectSubject

type DirectSubject = authzplan.DirectSubject

type FactSource

type FactSource interface {
	Names() TableNames
	Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error)
}

FactSource — то, поверх чего форма E вычисляет вердикт.

Интерфейс узкий намеренно: вердикт обязан быть ОДНИМ запросом, поэтому от источника нужен ровно один метод чтения и словарь имён.

type Fingerprint

type Fingerprint struct {
	// Composition — хэш отсортированного СПИСКА путей.
	Composition string
	// Content — хэш содержимого файлов в том же порядке.
	Content string
	// Files — сами пути, для сообщения гейта.
	Files []string
	// Predicate — ЧТО именно взято под отпечаток, словами.
	Predicate string
}

Fingerprint — отпечаток предмета замера.

func ComputeFingerprint

func ComputeFingerprint(root string) (Fingerprint, error)

ComputeFingerprint — отпечаток по дереву под root.

func (Fingerprint) FingerprintLines

func (fp Fingerprint) FingerprintLines(root string) string

FingerprintLines — строки отпечатка для шапки отчёта.

Пофайлово, а не одним числом: гейт, знающий только итоговый хэш, умеет сказать «что-то сдвинулось» и не умеет сказать ЧТО — а находка без координаты требует от читателя той же работы заново.

type Form

type Form string

Form — форма хранения права, которую прибор измеряет.

Значение осталось ОДНО. Их было шесть: пять описывали, как разложить выдачу в кортежи внешнего движка отношений (плоско, через группу, через отношение роли, через контейнер и композицией), шестая — реляционная — вычисляла вердикт запросом к БД. Движок снят целиком (S6), и вместе с ним снялись пять форм: они были формами хранения В НЁМ и измеряться иначе не могли.

Тип оставлен при одном значении намеренно, и это не «на будущее»: он читается — ячейка отчёта им подписана, и подпись «E-relational» отвечает на вопрос, к чему относятся числа. Отчёт, потерявший имя формы, стал бы таблицей чисел без предмета — а уже опубликованные отчёты (REPORT-*) подписаны именно так, и сопоставимость с ними держится этой подписью.

const FormE Form = "E-relational"

FormE — реляционная форма: вердикт вычисляется запросом к БД поверх зеркала объектов, внешнего движка отношений нет. Выдача — строка привязки, её субъекты и её правило-селектор: S + 2. Разворота в состав нет by construction.

type FullQuestion

type FullQuestion struct {
	Type     string
	Relation string
	Subject  string
	Object   string
	IsVerb   bool
}

FullQuestion — один вопрос полного вопросника.

func (FullQuestion) Name

func (q FullQuestion) Name() string

Name — имя вопроса в форме, годной для строки падения: тип, глагол, субъект, объект. Расхождение обязано называться поимённо, а не числом.

type LabelCell

type LabelCell struct {
	Form    string
	Place   string
	N       int
	Op      LabelOp
	Outcome Outcome
	Reason  string

	Repeats            int
	P50, P95, Min, Max float64 // мс работы

	StmtSQL  int
	StmtNote string // непусто ⇒ величина НЕ измерена, здесь причина
	Rows     int
	Parts    int

	// Окно неверного ответа. Заполняется только у операций-событий.
	HasWindow  bool
	WindowP50  float64
	WindowP95  float64
	WindowMax  float64
	WindowMin  float64
	Polls      int
	Revocation bool
}

LabelCell — одна ячейка (форма, N, операция).

func RunLabelPath

func RunLabelPath(ctx context.Context, w LabelWorld, f LabelForm, sc LabelScenario,
	cfg LabelConfig) []LabelCell

RunLabelPath снимает все операции пути меток для ОДНОЙ формы на ОДНОМ N.

Порядок несущий: контроли → L1 (первый ответ) → установившееся чтение → события. Контроли идут первыми, потому что все остальные величины опираются на то, что форма отвечает правильно; замер, начатый без них, мерил бы скорость неверного ответа.

type LabelConfig

type LabelConfig struct {
	WriteRepeats int
	ReadRepeats  int
	EventRepeats int
	// WindowBudget — сколько ждать верного ответа, прежде чем признать «не
	// выполнилось». Исчерпание бюджета НЕ печатается нулём.
	WindowBudget time.Duration
	// PollEvery — шаг опроса окна.
	PollEvery time.Duration
}

LabelConfig — повторы и бюджеты. Величины, у которых нет прод-аналога, заданы минимумом, которого требует методика, а не удобным числом.

func DefaultLabelConfig

func DefaultLabelConfig() LabelConfig

DefaultLabelConfig — повторов ровно столько, сколько нужно для p95 на выборке, которую не стыдно назвать выборкой.

type LabelEvent

type LabelEvent struct {
	Kind LabelEventKind
	// ObjectID — объект события. Пусто для отзыва правила: он про весь набор.
	ObjectID string
	// Expect — вердикт, который новое состояние ПОДРАЗУМЕВАЕТ. Окно кончается на
	// первом ответе, равном ему.
	Expect bool
}

LabelEvent — одно событие пути меток.

type LabelEventKind

type LabelEventKind string

LabelEventKind — что именно случилось с набором.

const (
	// EventEntered — объект, уже существовавший, получил метку правила.
	EventEntered LabelEventKind = "метка введена"
	// EventCreated — объект заведён и сразу несёт метку правила.
	EventCreated LabelEventKind = "объект создан под правилом"
	// EventLeft — объект потерял метку и вышел из-под правила.
	EventLeft LabelEventKind = "метка снята"
	// EventRuleRevoked — правило отозвано; набор выходит целиком.
	EventRuleRevoked LabelEventKind = "правило отозвано"
)

type LabelForm

type LabelForm interface {
	Name() string
	// Place — откуда сняты ячейки этой формы. Отчёт из двух мест без этого
	// признака выдаёт за один прогон два.
	Place() string
	// StmtProducer — чем и где считаются стейтменты этой формы и прошёл ли
	// производитель контроль в обе стороны.
	StmtProducer() ProducerStatus

	// ApplyRule записывает правило В ТОЙ ФОРМЕ, В КАКОЙ ОНО У НЕЁ СУЩЕСТВУЕТ:
	// у формы E — строки правила и выдачи.
	// Это и есть предмет L1.
	ApplyRule(ctx context.Context) (Counters, int, error)
	// DropRule снимает правило, не трогая общее. Нужен, чтобы повтор L1 мерил
	// первый ответ, а не идемпотентный повтор.
	DropRule(ctx context.Context) error

	// Settle — что форма ОБЯЗАНА сделать после уже закоммиченного общего
	// изменения, чтобы её ответ стал верным. У формы E ожидается ноль — и он
	// измеряется, а не объявляется.
	Settle(ctx context.Context, ev LabelEvent) (Counters, int, error)

	Check(ctx context.Context, subject, relation, objectID string) (bool, Counters, error)
	// Page сужает страницу кандидатов; возвращает число разрешённых и на сколько
	// частей страница была разложена.
	Page(ctx context.Context, subject, relation string, ids []string) (allowed, parts int, c Counters, err error)

	Teardown(ctx context.Context) error
}

LabelForm — форма, измеряемая на пути меток.

type LabelOp

type LabelOp string

LabelOp — операция пути меток.

const (
	// LopFirstAnswer — чего стоит ПЕРВЫЙ верный ответ, считая от момента, когда
	// правило написано. У формы E это запись правила и один вопрос; у формы,
	// материализующей состав, это была развёртка правила в кортежи —
	// материализация всего набора и один вопрос. Это и есть развилка N.
	LopFirstAnswer LabelOp = "L1-первый-ответ"
	// LopCheck — прямой вердикт в установившемся состоянии.
	LopCheck LabelOp = "D1-вердикт-1"
	// LopPage — страница договора (1000), суженная тем же правилом.
	LopPage LabelOp = "D2-страница-1000"
	// LopRelabelIn — метка одного объекта введена: он попадает под правило.
	LopRelabelIn LabelOp = "L2-метка-введена"
	// LopCreateInSet — объект создан и сразу попадает под существующее правило.
	LopCreateInSet LabelOp = "L3-создан-под-правилом"
	// LopRelabelOut — ОТЗЫВ: метка снята, объект выходит из-под правила.
	LopRelabelOut LabelOp = "L4-метка-снята-ОТЗЫВ"
	// LopRevokeRule — ОТЗЫВ: правило отозвано целиком, набор выходит разом.
	LopRevokeRule LabelOp = "L5-правило-отозвано-ОТЗЫВ"
)

type LabelReportInput

type LabelReportInput struct {
	Prov     Provenance
	Scenario LabelScenario
	Config   LabelConfig
	Ns       []int
	// QueueNote — что стоит между коммитом метки и началом пересчёта у продукта,
	// прочитанное из дерева. ТЕКСТ, а не число: складывать его с измеренным окном
	// запрещено правилом отнесения п.7.
	QueueNote string
	// RunCommand — чем прогон воспроизводится. Замер без него не свидетельство.
	RunCommand string
	// RepeatSchedule — сколько повторов снято на каждом N. Печатается строкой, а
	// не одним числом: расписание, уменьшающееся с ростом N, обязано быть видно,
	// иначе p95 на одном образце читается как p95 на пяти.
	RepeatSchedule string
	Unmeasured     []string
}

LabelReportInput — всё, что нужно отчёту, кроме самих ячеек.

type LabelScenario

type LabelScenario struct {
	N          int      // объектов под правилом
	Verbs      []string // M — глаголы роли, в канонической форме без приставки
	Subjects   []string // S — субъекты выдачи, в форме "user:usr-…"
	ObjectType string
	ProjectID  string
	AccountID  string
	LabelKey   string
	LabelValue string

	PageSize    int // страница договора
	Partition   int // объектов в одном обращении BatchCheck
	Parallelism int // частей страницы в полёте
}

LabelScenario — набор, правило и его размерности.

func (LabelScenario) Object

func (sc LabelScenario) Object(i int) string

Object — идентификатор i-го объекта набора. Форма фиксирована: сравниваются формы, а разница в идентификаторах была бы разницей в данных.

func (LabelScenario) Objects

func (sc LabelScenario) Objects() []string

Objects — весь набор.

func (LabelScenario) Ref

func (sc LabelScenario) Ref(objectID string) string

Ref — объект строкой «тип:идентификатор».

func (LabelScenario) SpareCreate

func (sc LabelScenario) SpareCreate() string

func (LabelScenario) SpareEnter

func (sc LabelScenario) SpareEnter() string

SpareEnter / SpareCreate — объекты СОБЫТИЙ, лежащие за пределами набора.

Отдельные, а не «возьмём любой из набора»: событие, поставленное на объект набора, меняло бы сам набор от повтора к повтору, и вторая итерация мерила бы уже другой мир.

type LabelWorld

type LabelWorld interface {
	// Commit применяет общее изменение и возвращает МОМЕНТ его коммита — точку,
	// от которой отсчитывается окно неверного ответа у КАЖДОЙ формы.
	Commit(ctx context.Context, ev LabelEvent) (time.Time, error)
	// Revert возвращает общее состояние к тому, каким оно было до события.
	Revert(ctx context.Context, ev LabelEvent) error
}

LabelWorld — ОБЩАЯ часть обеих форм: зеркало объектов и цепь предков.

Реализуется тем, кто держит обе стороны сравнения. Границей она заведена не ради красоты: без неё общее изменение выполнял бы каждый по-своему, и правило отнесения п.2 («общее не приписывается никому») стало бы обещанием вместо свойства прогона.

type Model

type Model = authzplan.Model

type ModelType

type ModelType = authzplan.ModelType

type Op

type Op string

Op names one measured operation.

const (
	OpGrant    Op = "W1-grant"         // materialize the binding over N objects
	OpRevoke   Op = "W2-revoke-1"      // withdraw ONE subject
	OpRelabel1 Op = "W3-relabel-1"     // one object enters the set
	OpRelabelK Op = "W4-relabel-K"     // K objects enter the set
	OpCheck    Op = "R1-check"         // one object, one verb, one subject
	OpPage50   Op = "R2-one-partition" // ONE BatchCheck request (size = cfg.Partition)
	OpPageFull Op = "R3-page-full"     // a whole page: cfg.PageSize, partitioned+parallel
	OpVolume   Op = "V-volume"         // tuples and bytes

	// Операции, которых у движка отношений нет by construction, и каскад.
	//
	// Они заведены в СЛОВАРЕ, а не описаны одной прозой: ячейка, которой нет в
	// словаре, не входит в сумму категорий и не печатается — отчёт был бы полон
	// по своему собственному счёту и молчал бы о самых дорогих вопросах.
	OpInlineGrant  Op = "T-inline-grant"  // выдача в той же транзакции, что и предмет выдачи
	OpInlineRevoke Op = "T-inline-revoke" // отзыв в той же транзакции
	OpCascade      Op = "C-cascade"       // вопрос каскадного принципала (три верхних уровня)
)

type Outcome

type Outcome string

Outcome — три категории, никогда не две.

«Не выполнилось» — то, что замер больше всего хочет потерять: форма, чей стек не поднялся, чья операция вышла за срок, чьё хранилище отвергло запрос, НЕ победила. Категория доносится до отчёта вместе с причиной, никогда не усредняется, не опускается и не засчитывается никому в пользу.

Категорий было ЧЕТЫРЕ, и обе снятые ушли вместе с движком отношений, а не были упрощены:

  • `refused` — «движок ответил отказом» — был фактом о движке, и производителем его была ошибка его HTTP-API;
  • `not-applicable` — «операции у формы нет by construction» — был самым содержательным результатом таблицы: у движка не могло быть общей транзакции с БД предмета выдачи. У формы E эта операция ВЫРАЗИМА, поэтому ячейка теперь измеряется, а не объявляется неприменимой.

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

const (
	Measured Outcome = "measured"
	NotRun   Outcome = "not-run" // ничего не измерено; причина говорит, почему
)

type Plan

type Plan = authzplan.Plan

type ProducerStatus

type ProducerStatus struct {
	Place    string // где снимается
	Producer string // чем снимается
	OK       bool   // прошёл ли контроль в обе стороны
	Note     string // что именно наблюдалось — печатается и при OK, и при провале
}

ProducerStatus — состояние ПРОИЗВОДИТЕЛЯ величины `StmtSQL` на одном месте снятия.

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

func (ProducerStatus) String

func (p ProducerStatus) String() string

type Provenance

type Provenance struct {
	When        string
	TreeRev     string
	Machine     string
	Postgres    string // образ, на котором СНЯТ замер
	ModelPath   string
	ModelDigest string

	// StmtProducers — состояние производителя `StmtSQL` по КАЖДОМУ месту снятия.
	// Величина, у входа которой нет производителя, зеленеет молча — поэтому это
	// печатается всегда, и при успехе, и при провале.
	StmtProducers []ProducerStatus
	// CascadeChain — объявленная цепь обхода каскада и её глубина.
	CascadeChain string
	CascadeDepth int
}

Provenance is what makes a number evidence rather than an anecdote.

Здесь стояли три поля движка отношений — его образ, образ его командной строки и ИЗМЕРЕННЫЙ потолок его пакетной проверки. Все три сняты вместе с ним. Потолок снят с особым сожалением и потому назван: он был измерен на живом сервере, а не взят из чужого утверждения, — ровно потому, что дерево пинило движок в двух местах разными версиями и спорить о том, чья версия верна, было не нужно, когда можно спросить. Предмета спора больше нет.

func CollectProvenance

func CollectProvenance(postgres, modelPath, modelDigest string) Provenance

CollectProvenance собирает то, без чего число — анекдот.

Стека здесь больше НЕ спрашивают, и это не упрощение подписи: мест снятия два — своя посадка прибора и продуктовые таблицы iam (прогон Ф5), — и второе про стек прибора не знает вовсе. Прежняя подпись брала `*Stack` и заполняла из него образ Postgres; прогон Ф5 при этом поднимал стек, которым НЕ ПОЛЬЗОВАЛСЯ ни одной операцией, только чтобы было что передать. Образ теперь называет тот, кто на нём меряет.

type Relation

type Relation = authzplan.Relation

type RelationalVerdict

type RelationalVerdict struct {
	Src FactSource
}

RelationalVerdict — вердикт формы E.

func (RelationalVerdict) Allowed

func (v RelationalVerdict) Allowed(ctx context.Context, subject, verb, objectType string,
	objectIDs []string) (map[string]bool, error)

Allowed отвечает на вопрос о доступе для ЦЕЛОЙ страницы идентификаторов ОДНИМ запросом.

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

type RightsStore

type RightsStore interface {
	// Seed кладёт структурные строки и (когда grant непуст) строки выдачи.
	// Засев не входит ни в одну измеряемую величину.
	Seed(ctx context.Context, structural, grant []Tuple) (Counters, error)

	// Write и Remove — измеряемые мутации: выдача, переразметка, отзыв.
	Write(ctx context.Context, tuples []Tuple) (Counters, error)
	Remove(ctx context.Context, tuples []Tuple) (Counters, error)

	// InlineGrant пишет ПРЕДМЕТ выдачи и саму выдачу в ОДНОЙ транзакции.
	//
	// Операция существует потому, что у формы E она ВЫРАЗИМА: выдача и её предмет
	// лежат в одной базе. Прежде эта ячейка была содержательна своей пустотой у
	// второй стороны — общей транзакции между БД сервиса и чужим движком не
	// бывает; сегодня она измеряется как абсолютная величина, и «шесть
	// стейтментов» — утверждение о форме, а не о разнице с кем-то.
	InlineGrant(ctx context.Context, data, grant []Tuple) (Counters, error)
	// InlineRevoke — то же для отзыва: отзыв, не зависящий от доставки.
	InlineRevoke(ctx context.Context, data, revoke []Tuple) (Counters, error)

	Check(ctx context.Context, subject, verb, object string) (bool, Counters, error)
	BatchCheck(ctx context.Context, subject, verb string, objects []string) ([]bool, Counters, error)
	// CheckPage разрешает целую страницу и возвращает, на сколько частей она была
	// разложена. Число частей ОБЪЯВЛЯЕТСЯ, а не усредняется: у формы E оно равно
	// единице, и единица, полученная замером, отличима от единицы по умолчанию.
	CheckPage(ctx context.Context, subject, verb string, objects []string,
		partition, parallel int) (verdicts []bool, parts int, c Counters, err error)

	Volume(ctx context.Context) (Volume, error)

	// StmtProducer называет, чем и где снимается `StmtSQL` у этого хранилища и
	// прошёл ли производитель контроль в обе стороны.
	StmtProducer() ProducerStatus

	// Place — из какого места снята каждая ячейка этого хранилища. Отчёт,
	// собранный из двух мест без этого признака, выдаёт за один прогон два.
	// Признак пережил вторую сторону: места по-прежнему два — своя посадка
	// прибора и продуктовые таблицы iam (прогон Ф5), — и путать их нельзя.
	Place() string

	Teardown(ctx context.Context) error
}

RightsStore — граница «хранилище прав»: то единственное, о чём знает измеряющий код.

Граница заводилась ради того, чтобы форма E вообще подключилась к матрице: прежний конструктор требовал модель авторизации для каждой формы, а засев возвращал конкретный тип клиента движка. Реализация у неё теперь ОДНА (`relStore`), и это не повод её снимать: она перечисляет операции, которые прибор измеряет, и держит их подписи в одном месте — измеряющий код о том, чем форма отвечает, по-прежнему не знает.

type Runner

type Runner struct {
	Stack *Stack
	Cfg   Config

	Notes map[Form]string
}

Runner executes the matrix.

func NewRunner

func NewRunner(st *Stack, cfg Config) *Runner

NewRunner готовит прогон.

Прежде конструктор ГОТОВИЛ МОДЕЛИ: для каждой формы он выводил из канонического текста её DSL и гнал его через внешний преобразователь в JSON. Форме E модель не требуется — вердикт она вычисляет запросом, — и «модель не требуется» было законным ответом, отделённым от ошибки «неизвестная форма» намеренно, чтобы первое не покрывало опечатку во втором. Модели готовились для пяти форм движка; движка нет, и готовить нечего — вместе с этим сняты сам ответ-сентинел и хранилище моделей.

Конструктор оставлен, а не свёрнут в литерал: он остаётся единственным местом, где `Notes` заводится непустой картой, и вызывающему не приходится знать, что её надо создать.

func (*Runner) NewSeededStore

func (r *Runner) NewSeededStore(ctx context.Context, f Form, sc Scenario, grant bool, name string) (RightsStore, error)

NewSeededStore builds a store for `f`, seeds the structural tuples and (when `grant`) the shape's grant tuples. Returned ready to be asked questions.

func (*Runner) RunReads

func (r *Runner) RunReads(ctx context.Context, f Form, sc Scenario) []Cell

RunReads measures R1..R3 for one (form, N) against a single seeded store.

func (*Runner) RunWrites

func (r *Runner) RunWrites(ctx context.Context, f Form, sc Scenario) []Cell

RunWrites measures W1..W4 and the volume for one (form, N).

Each repeat gets a FRESH store: a write measured against a store that already carries the tuples is measuring an idempotent no-op, which is a different operation wearing the same name.

type SQLStmtCounter

type SQLStmtCounter struct {
	// contains filtered or unexported fields
}

SQLStmtCounter — счётчик стейтментов на СВОЁМ пуле pgx.

Экспортируется потому, что сторона формы E живёт там, где достижимы её таблицы, а прибор — здесь. Своего счётчика там заводить нельзя: два счётчика одного предмета расходятся молча, и расходятся они как раз в ту сторону, где расхождение не видно.

func NewSQLStmtCounter

func NewSQLStmtCounter() *SQLStmtCounter

NewSQLStmtCounter заводит счётчик.

func (*SQLStmtCounter) Open

func (c *SQLStmtCounter) Open() StmtWindow

Open открывает окно.

func (*SQLStmtCounter) Tracer

func (c *SQLStmtCounter) Tracer() pgx.QueryTracer

Tracer — то, что вешается на конфигурацию пула.

func (*SQLStmtCounter) Verify

func (c *SQLStmtCounter) Verify(ctx context.Context, pool *pgxpool.Pool, place string) ProducerStatus

Verify прогоняет контроль в ОБЕ стороны на живом пуле и возвращает состояние производителя. Пока контроль не пройден, величина не печатается вовсе — ноль от несработавшего счётчика неотличим от измеренного нуля.

type Scenario

type Scenario struct {
	N        int      // objects inside the selected set
	Spare    int      // objects OUTSIDE it, kept for the relabel operations
	Verbs    []string // M — the verbs the role grants
	Subjects []string // S — full principal strings, e.g. "user:u000"
	Role     string   // "viewer" | "editor" — selects the C/D grant relation
}

Scenario is the ONE dataset every shape is measured against.

func NewScenario

func NewScenario(n, spare, subjects int, role string, verbs []string) Scenario

NewScenario builds the dataset. Subjects are `user:` principals because that is what a tenant binding names; `service_account:` resolves through the same direct userset and would not change the shape of the graph.

func (Scenario) ForeignObject

func (sc Scenario) ForeignObject() string

ForeignObject — объект проекта ЧУЖОГО аккаунта, ПОМЕЧЕННЫЙ меткой набора.

Метка обязательна и она здесь главное. Без неё отказ на этом объекте объясняется правилом-селектором, и конъюнкт области снова остаётся непроверенным — вопрос стал бы вторым экземпляром уже покрытого «объект вне набора». С меткой у формы E выполнены ВСЕ прочие конъюнкты (субъект назван в привязке, роль даёт глагол, метка объекта под селектор попадает), и отказать обязана ровно область.

Идентификатор намеренно не из нумерации `Object(i)`: объект чужого арендатора не должен попадать ни в набор, ни в запасные — иначе он уехал бы в выдачу, страницу или переразметку и сдвинул бы базу сравнения пяти форм.

func (Scenario) LabelledInMirror

func (sc Scenario) LabelledInMirror() map[string]bool

LabelledInMirror — объекты, чья строка зеркала несёт метку набора.

Метку ставит ВЛАДЕЛЕЦ объекта, а не выдача: зеркало существует независимо от того, выдано ли кому-нибудь право. Поэтому чужой объект помечен так же, как свои, — и это не поблажка форме E, а единственный способ спросить её про область, не подсказав ответ другим конъюнктом.

func (Scenario) Object

func (sc Scenario) Object(i int) string

Object returns the id of the i-th object. Objects [0,N) are inside the selected set; [N, N+Spare) are outside it.

func (Scenario) Objects

func (sc Scenario) Objects() []string

Objects returns the in-set objects.

func (Scenario) Structural

func (sc Scenario) Structural() []Tuple

Structural returns the parent-pointer tuples. They are IDENTICAL in every shape and are therefore excluded from the grant accounting: attributing them to a shape would flatter whichever shape has fewest grant tuples.

Второй аккаунт, его проект и один объект в нём — тоже структурное: они не входят ни в набор, ни в запасные, поэтому ни одна операция записи их не касается и величина выдачи каждой формы остаётся прежней. Проверено прогоном, а не рассуждением: строки выдачи всех шести форм до и после этой правки совпали, а структурная часть выросла ровно на три строки у каждой (см. `ExpectedStructuralRows`).

type Stack

type Stack struct {
	// DSN — строка подключения к этой базе. Хранилища формы E берут в ней по схеме.
	DSN string
	// Postgres — образ, который РЕАЛЬНО поднялся, а не тот, что задумывался.
	Postgres string
	// contains filtered or unexported fields
}

Stack — Postgres, на котором прибор меряет форму E, и ничего кроме.

Один стек на процесс: изоляцию между кейсами даёт СХЕМА внутри этой базы, и платить контейнером за кейс значило бы платить за изоляцию, которая уже есть, больше, чем стоит измеряемое. Это же свойство держит санкцию гейта `TestNoPackageStartsAContainerPerTest`, и доказано оно исходом — `isolation_test.go`, а не объявлением здесь.

Прежде стек нёс ЧЕТЫРЕ предмета: сеть, Postgres движка отношений, сам движок и отдельный Postgres формы E. Первые три сняты вместе с движком; четвёртый остался и стал единственным. Отдельным он заводился затем, чтобы буферы, занятые кортежами движка, не наказывали чтение формы E за чужие данные, — довод пережил свой предмет, но не свою посадку: база по-прежнему своя, и числа остаются сопоставимыми с уже опубликованными отчётами.

func SharedStack

func SharedStack(ctx context.Context) (*Stack, error)

SharedStack поднимает стек при первом обращении и отдаёт его каждому следующему.

Неудача подъёма возвращается КАЖДОМУ вызывающему, а не только тому, кто проиграл гонку за `Once`: иначе остальные пошли бы работать по пустому адресу и отчитались бы о форме как о «медленной» там, где не было поднято ничего.

type StmtWindow

type StmtWindow struct {
	// contains filtered or unexported fields
}

StmtWindow — окно счёта.

func (StmtWindow) Close

func (s StmtWindow) Close() int

Close закрывает окно и возвращает число стейтментов.

type TableNames

type TableNames struct {
	Mirror          string // зеркало объектов: тип · идентификатор · оба родителя · метки
	Projects        string // проект → аккаунт
	Accounts        string // аккаунт → кластер
	Bindings        string // выдача: область и роль
	BindingSubjects string // субъекты выдачи
	Selectors       string // правило-селектор выдачи (по меткам)
	RoleVerbs       string // роль → глаголы, которые она даёт
	AccountAdmins   string // каскад, уровень 3
	ClusterAdmins   string // каскад, уровни 1-2
}

TableNames — имена таблиц источника фактов.

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

type Term

type Term = authzplan.Term

type TermKind

type TermKind = authzplan.TermKind

type Tuple

type Tuple struct {
	User     string
	Relation string
	Object   string
}

Tuple — ЕДИНИЦА НАМЕРЕНИЯ: «такой-то субъект связан таким-то отношением с таким-то объектом».

Тип переехал сюда из снятого транспорта к движку отношений и потерял вместе с ним две вещи: теги JSON (сериализовать его больше некому — на wire он не уходит) и поле условия (условие на кортеже — свойство движка, и форма E его не выражает: строка не может отвечать по-разному на два одинаковых запроса).

Он остаётся ОБЩИМ СЛОВАРЁМ, а не формой хранения: производители намерения (`Grant`, `RelabelOne`, `RevokeSubject`, `InlineIntent`) говорят на нём, а хранилище переводит его в свои строки у себя. Словарь пережил свою вторую сторону намеренно — числа сегодняшних прогонов остаются сопоставимыми с уже опубликованными отчётами, потому что намерение осталось тем же.

func CascadeSeed

func CascadeSeed(_ Form, _ Scenario) []Tuple

CascadeSeed — принципал трёх верхних уровней доступа.

Строка администратора аккаунта, которую запрос вердикта достаёт соединением ограниченной глубины по родительским указателям (leaf → project → account → cluster). Каскад меряется отдельной ячейкой, потому что это и есть путь, которым чинят аварию: он разрешается в момент запроса и не зависит от того, доехала ли до кого-нибудь материализация.

func Grant

func Grant(f Form, sc Scenario) []Tuple

Grant returns the tuples that materialize the binding over the in-set objects.

Выдача формы E — строка привязки, её субъекты и её правило-селектор. Объекты набора здесь НЕ перечисляются: метка живёт в зеркале, зеркало наполняют владельцы объектов, и выдача узнаёт набор запросом. Отсюда и то, что «выдать» у неё стоит S + 2 независимо от N — постоянство и есть измеряемый результат, а не отказ прибора.

func GrantScoped

func GrantScoped(sc Scenario, scope string) []Tuple

GrantScoped — та же выдача, но на названной области.

Существует ради аккаунтной ветви предиката области, и «проверяется» здесь означает УТВЕРЖДЕНИЕ, а не исполнение. У ветви есть пара: TestAccountScopedGrantReachesObjectsOfItsProjects требует положительного (аккаунтная выдача накрывает объект проекта СВОЕГО аккаунта) и отрицательного (не накрывает помеченный объект проекта ЧУЖОГО аккаунта). Прежняя редакция этого комментария обещала проверку, которой не было: ветвь исполнялась, но ни одно утверждение от неё не зависело — снятие корреляции области не роняло ни одной пробы.

func InlineIntent

func InlineIntent(sc Scenario, obj string) (data, grant []Tuple)

InlineIntent — предмет выдачи и сама выдача для операций «в одной транзакции».

Предмет — новый объект (строка зеркала), выдача — привязка на него. У движка отношений эта пара неприменима by construction, и ячейка сообщает именно это.

func InlineRevokeIntent

func InlineRevokeIntent(sc Scenario, obj string) (data, revoke []Tuple)

InlineRevokeIntent — отзыв одного субъекта встраиваемой привязки, в той же транзакции, что и правка предмета выдачи.

func RelabelMany

func RelabelMany(f Form, sc Scenario, objs []string) []Tuple

RelabelMany is RelabelOne applied to K objects — the "mass re-tagging" operation.

func RelabelOne

func RelabelOne(f Form, sc Scenario, obj string) []Tuple

RelabelOne returns the tuples written when ONE object ENTERS the selected set — the "a label changed on one resource" operation.

Это ось, на которой формы расходились сильнее всего и на которой плоский индекс платил хуже прочих: он переписывал всё произведение глаголов на субъекты для этого объекта. У формы E это правка метки ОДНОЙ строки зеркала, и величина от N не зависит.

func RevokeSubject

func RevokeSubject(f Form, sc Scenario, subject string) []Tuple

RevokeSubject returns the tuples DELETED when ONE subject loses the binding.

Отзыв меряется отдельно от выдачи намеренно: путь материализации, умеющий только ДОБАВЛЯТЬ, зелен на каждом утверждении «было ли выдано» и неверен ровно на той операции, ради которой отзыв существует (см. .claude/rules/testing.md §«Параллельный newman», дискриминатор create-vs-update).

type Volume

type Volume struct {
	GrantRows       int64
	GrantBytes      int64
	StructuralRows  int64
	StructuralBytes int64
	TableBytes      int64
}

Volume — ёмкостная сторона ячейки.

Смысл колонок закреплён здесь, потому что у двух из них он неочевиден:

  • GrantRows — строки ВЫДАЧИ: привязка, её субъекты и её правило-селектор;
  • GrantBytes — логические байты ВСЕХ строк хранилища формы, включая структурные. Асимметрия «строки без структурных, байты со структурными» унаследована от снятой базы сравнения и сохранена намеренно: числа сегодняшних прогонов остаются сопоставимыми с уже опубликованными отчётами (REPORT-*), которые считались этим же правилом. Чтобы асимметрия не читалась молча, структурная часть вынесена в соседнюю пару колонок и печатается рядом;
  • StructuralRows/Bytes — зеркало объектов и цепь предков: то, что у формы есть независимо от того, выдано ли кому-нибудь право;
  • TableBytes — весь размер отношений с индексами. Индекс табличный, а не построковый, поэтому на строку он не делится и печатается отдельно.

Jump to

Keyboard shortcuts

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