privileged

package
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 10, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Overview

Package privileged is wowapi's scoped privileged-service surface: the sanctioned, audited way a module performs a valid tenant-scoped operation that requires PLATFORM privilege at the database, WITHOUT the module writing its own SECURITY DEFINER SQL and WITHOUT ever seeing a platform pool or raw SQL door (SEC-24 / SEC-13; GAP-006).

Why this exists

Two framework tables are deliberately off-limits to the shared app_rt role modules run as:

  • relationships — a granted_via edge is an AUTHORIZATION INPUT, so app_rt holds SELECT only; writes are app_platform (migration 00005).
  • rule_versions — ACTIVATION changes runtime behavior, so app_rt holds SELECT,INSERT (propose drafts) only; activation UPDATE is app_platform (migration 00008).

A product that needs to grant an edge or activate a tenant rule version could previously only bridge the gap with a per-product SECURITY DEFINER function, re-implementing tenant binding, resource existence, type/key ownership, scope restriction, audit, and race handling every time — risky and unaudited.

How it stays safe

Each Services value is bound to ONE owning module at construction. Every operation runs in a PLATFORM transaction that is nonetheless TENANT-BOUND (TxManager.WithTenant over the app_platform pool): app_tenant_id() resolves to the caller's tenant, so the relationships/rule_versions RLS WITH CHECK holds exactly as it did for the SECURITY DEFINER bridges, while the platform grants permit the write. In Go, before the write, the service enforces:

  • tenant binding (caller ctx must carry a tenant; else fail closed);
  • relationship-type / rule-key OWNERSHIP — the key must be prefixed with the owning module name, or appear in a declared allow-list (mirrors how seeds, resource types, and rule points validate key ownership);
  • subject/object RESOURCE EXISTENCE in the bound tenant;
  • SCOPE restriction (rule versions must be tenant-scope and belong to the bound tenant);
  • AUDIT metadata via the kernel audit hash chain, in the same tx;

and it relies on the existing DB invariants — RLS tenant isolation, the rule_versions one-active-per-instant EXCLUDE constraint, row locks — for the concurrency guarantees the bridges depended on. No new GRANT is added to any table; the security posture of migration 00005/00008 is untouched.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ActivateOptions

type ActivateOptions struct {
	Gate func(ctx context.Context, db database.TenantDB) error
}

ActivateOptions carries optional, product-supplied activation gates. Gate, when set, runs INSIDE the activation transaction after the framework's ownership / scope / tenant checks pass but before the supersede+activate write — so a product can enforce a domain rule (e.g. "a verified citation must cover the effective date") atomically, without a SECURITY DEFINER bridge. Returning an error aborts and rolls back the activation. The framework stays domain- agnostic: it never interprets the gate, only runs it in the right tx position.

type Config

type Config struct {
	AllowRelTypes []string
	AllowRuleKeys []string
}

Config declares a module's extra (non-prefixed) ownership grants. A module always owns keys prefixed "<module>."; AllowRelTypes / AllowRuleKeys widen that set with explicit keys it is permitted to operate on (e.g. a kernel "core." relationship type a module is sanctioned to grant). Empty is the common case: prefix ownership only.

type GrantSpec

type GrantSpec struct {
	RelType     string
	SubjectKind string
	SubjectID   uuid.UUID
	Object      resource.Ref
	ValidFrom   time.Time
	ValidTo     *time.Time
	Actor       uuid.UUID
}

GrantSpec describes an edge to create. RelType must be a relationship type the module owns (prefix or allow-list). Subject is the edge's subject side (kind + id, e.g. a capacity); Object is the resource the edge points at. The optional temporal window [ValidFrom, ValidTo) matches the framework edge shape; a zero ValidFrom means "now", a nil ValidTo means "open-ended". Actor is recorded as created_by and in the audit trail.

type Relationships

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

Relationships is the scoped privileged service for ReBAC relationship edges. It lets a module GRANT and REVOKE edges of a relationship type it owns, running with app_platform write privilege but tenant-bound so RLS still isolates. It absorbs, framework-side, every check the product SECURITY DEFINER bridge (identity_grant/revoke_committee_seat) performed.

func (*Relationships) Grant

func (r *Relationships) Grant(ctx context.Context, spec GrantSpec) (uuid.UUID, error)

Grant creates the relationship edge and writes an audit row, atomically, in a tenant-bound app_platform transaction. It enforces (in order): a bound tenant; module ownership of RelType; a valid temporal window; existence of the subject (an active acting capacity, when SubjectKind is capacity) and of the object resource — both in the caller's bound tenant. Returns the new edge id.

Concurrency: two concurrent grants each insert their own edge (edges are many-cardinality by default); tenant isolation and the resource-existence checks are re-evaluated inside the transaction under the same snapshot as the insert, so a resource deleted concurrently cannot slip a dangling edge past the FK/RLS. This reproduces the bridge's guarantee without a product function.

func (*Relationships) Revoke

func (r *Relationships) Revoke(ctx context.Context, id, actor uuid.UUID) error

Revoke soft-revokes an owned edge: it sets valid_to = now() (never deletes, so the historical grant survives — audit-friendly, matching the bridge's identity_revoke_committee_seat), bumps version, and writes an audit row. It enforces a bound tenant, that the edge's rel_type is owned by this module, and tenant scope (RLS + an explicit re-check under FOR UPDATE). A missing or already-revoked edge is reported; a double-revoke is a no-op conflict rather than a silent success.

type Rules

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

Rules is the scoped privileged service for tenant-scope rule-version activation. It lets a module activate a draft version of a rule KEY it owns, but only a TENANT-SCOPE version belonging to the caller's bound tenant — platform-scope activation stays platform-tooling-only. It absorbs, framework- side, the checks the product SECURITY DEFINER bridge (policy_activate_rule_version) performed, and delegates the supersede+activate state machine to the kernel rules.Store so the one-active-per-instant EXCLUDE constraint keeps arbitrating races.

func (*Rules) ActivateTenant

func (r *Rules) ActivateTenant(ctx context.Context, versionID, approvedBy uuid.UUID, opts ActivateOptions) error

ActivateTenant activates a tenant-scope rule version the module owns, in a tenant-bound app_platform transaction, and writes an audit row. It enforces (in order): a bound tenant; that the version exists; module ownership of its rule_key; that the version is TENANT scope AND belongs to the bound tenant (bridge check policy_activation_scope_denied — cross-tenant / platform-scope activation is refused); the draft/pending transition (delegated to the store); an optional product Gate; then supersede+activate via rules.Store.Activate.

Concurrency: two concurrent activations of overlapping versions at the same (key, tenant, scope) both call the store's supersede+activate; the rule_versions one-active-per-instant EXCLUDE constraint makes the loser fail with a conflict (23P01) rather than both becoming active — the same arbitration the bridge relied on.

type Services

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

Services is the per-module bundle of scoped privileged services handed to a module through module.Context. It is bound to a single owning module (name) and enforces that module's ownership of every key it operates on.

func New

func New(module string, platformTx database.TxManager, store *rules.Store, audit *kaudit.Writer, idgen model.IDGen, cfg Config) *Services

New builds the privileged services for one module over the PLATFORM transaction manager (the app_platform pool). platformTx MUST be a tenant-bindable manager whose WithTenant runs as app_platform — the role that holds the relationships/rule_versions write grants; passing the app_rt manager would fail closed at the DB. audit and idgen are the shared kernel instances.

func (*Services) Relationships

func (s *Services) Relationships() *Relationships

Relationships returns the ReBAC relationship-edge service (Grant / Revoke).

func (*Services) Rules

func (s *Services) Rules() *Rules

Rules returns the tenant-scope rule-version activation service.

Jump to

Keyboard shortcuts

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