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 ¶
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 ¶
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 ¶
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 ¶
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).