application capa

application-rbac

Forge module wrapping identity-rbac.

Forge module wrapping identity-rbac.

Tierapplication
Roleunclassified (baselined)
Pathcrates/application/rbac
Edition2024
Targetsapplication_rbac, pg_forge_rbac
Public items34 across 6 modules
Tests16

What it is for

application-rbac — wraps identity-rbac.

Sprint 65: also owns the role-graded authorization layer (authz) and app-side role persistence (repo) lifted from rust-cms-engine. Per Gate 1.5 Q3 the ideal end-state distributes per-module permission-code gates to their owning module crates; during this extraction sprint they live here verbatim (scope discipline — see TECH-DEBT "distribute authz gates").

B-009: before this, migrations() returned Vec::new() and migrate_pending returned Ok(0) while ignoring the pool, so roles, user_roles, permissions, role_permissions (crates/identity/rbac/migrations/001_roles.sql003_permissions.sql) were never created by anything that actually called this module's own migration path.

Documented dependency gap (Gate 0.5 investigation, matches application-catalog's documented-not-guessed pattern): all three files' updated_at triggers call update_updated_at_column(), which identity-rbac's own migrations never define — it is owned by foundation-basemodels (crates/foundation/basemodels/migrations/001_base_model_infrastructure.sql) and, separately, redefined in the central application-engine platform set (crates/application/engine/migrations/021_rbac_foundation.sql). No forge-basemodels module exists to compose that dependency automatically. A host mounting application-rbac standalone on a fresh DB must apply foundation-basemodels's migration (or otherwise define update_updated_at_column()) before calling migrate_pending here, or all three files will fail at their CREATE TRIGGER statements.

Also note: 021_rbac_foundation.sql already vendors roles and user_roles (with IF NOT EXISTS, unlike this module's own 001_roles.sql/002_user_roles.sql, which use bare CREATE TABLE) — per B-009's own documented "double-applying migrations" risk, this module's own migration path is therefore usable standalone on a fresh DB, but NOT after the central platform set has already run, until that risk is reconciled. permissions/role_permissions (003) have no central-set equivalent, so no such conflict there.

No permission_codes() added: identity-rbac (crates/identity/rbac/src/permission.rs) models permissions as a dynamic module.action codename, not a fixed set of constants — there is nothing to enumerate without inventing names.

Capabilities

RbacModule

application-rbac — wraps identity-rbac.

Item
pub struct RbacModule
RbacModule :: fn new() -> Self
RbacModule :: fn name(& self) -> & 'static str
RbacModule :: fn version(& self) -> & 'static str
RbacModule :: fn migrations(& self) -> Vec <MigrationSet>
RbacModule :: async fn migrate_pending(& self, pool : & PgPool) -> std::result::Result <u32, MigrationError>

ActiveTenant

Active-tenant context for a request (Sprint 0.55 US-0.55.8b, model D).

Item
pub struct ActiveTenant
ActiveTenant :: fn resolve(session_active : Option <Uuid>, memberships : Vec <Uuid>, is_global : bool) -> Self
ActiveTenant :: fn rls_setting(& self) -> String
ActiveTenant :: fn tenant_scope(& self) -> TenantScope
ActiveTenant :: fn rls_setting_with(& self, want_all : bool) -> String
ActiveTenant :: fn tenant_scope_with(& self, want_all : bool) -> TenantScope
ActiveTenant :: fn is_member_of(& self, tenant_id : Uuid) -> bool
ActiveTenant :: async fn from_request_parts(parts : & mut Parts, _state : & S) -> Result <Self, Response>

authz (other)

Role-graded authorization (Sprint 18 T242, US-105).

Item
pub const LEVEL_SUPERADMIN: i32
pub const LEVEL_ADMIN: i32
pub const LEVEL_EDITOR: i32
pub const LEVEL_MEMBER: i32
fn check_hierarchy(actor_level : i32, required_level : i32) -> bool
fn can_manage_user(actor_level : i32, target_level : i32) -> bool

CurrentUser

Role-graded authorization (Sprint 18 T242, US-105).

Item
pub struct CurrentUser
CurrentUser :: fn is_superadmin(& self) -> bool
CurrentUser :: async fn from_request_parts(parts : & mut Parts, _state : & S) -> Result <Self, Response>

UserPermissions

Role-graded authorization (Sprint 18 T242, US-105).

Item
pub struct UserPermissions
UserPermissions :: fn from(set : std::collections::HashSet <String>) -> Self
UserPermissions :: fn has(& self, code : & str) -> bool

repo::rbac (other)

Role / role-assignment persistence over roles + user_roles

Item
async fn primary_role_for_user(pool : & PgPool, user_id : Uuid,) -> Result <Option <Role>, sqlx::Error>
async fn list_roles(pool : & PgPool) -> Result <Vec <Role>, sqlx::Error>
async fn role_by_id(pool : & PgPool, id : Uuid) -> Result <Option <Role>, sqlx::Error>
async fn set_user_role(conn : & mut PgConnection, user_id : Uuid, role_id : Uuid, assigned_by : Option <Uuid>,) -> Result <(), sqlx::Error>

services::rbac (other)

RBAC service — permission resolution + bootstrap seed.

Item
async fn resolve_permissions(pool : & PgPool, user_id : Uuid,) -> Result <HashSet <String>, sqlx::Error>
async fn all_permission_codenames(pool : & PgPool) -> Result <HashSet <String>, sqlx::Error>

BootstrapError

RBAC service — permission resolution + bootstrap seed.

Item
async fn ensure_bootstrap_admin(pool : & PgPool) -> Result <Option <Uuid>, BootstrapError>
pub enum BootstrapError

How to use it

No examples/ target and no doctest in this crate's rustdoc. The tests listed under Verification are the closest executable usage.

Module structure

application_rbac

flowchart TD
  n_application_rbac["application_rbac"]
  n_application_rbac --> n_active_tenant["active_tenant"]
  n_application_rbac --> n_authz["authz"]
  n_application_rbac --> n_repo["repo"]
  n_repo --> n_repo__rbac["rbac"]
  n_application_rbac --> n_services["services"]
  n_services --> n_services__rbac["rbac"]

Public surface

`crate root`

ItemWhat it is
pub struct RbacModule
RbacModule :: fn new() -> Self
RbacModule :: fn name(& self) -> & 'static str
RbacModule :: fn version(& self) -> & 'static str
RbacModule :: fn migrations(& self) -> Vec <MigrationSet>
RbacModule :: async fn migrate_pending(& self, pool : & PgPool) -> std::result::Result <u32, MigrationError>

`active_tenant`

ItemWhat it is
pub struct ActiveTenantThe tenant a request operates in, plus the actor's tenant capabilities.
ActiveTenant :: fn resolve(session_active : Option <Uuid>, memberships : Vec <Uuid>, is_global : bool) -> SelfResolve the active tenant from the session's stored selection
ActiveTenant :: fn rls_setting(& self) -> StringThe value to bind to app.current_tenant_id for the default request scope (matches the fail-closed RLS policy in 002_audit_hardening.sql): - an active tenant → scoped to it, - else a global actor → '*' (see-all), - else → "" (fail-closed: matches no row, not see-all).
ActiveTenant :: fn tenant_scope(& self) -> TenantScopeThe typed scope this actor runs under (Sprint 3.2 D1)
ActiveTenant :: fn rls_setting_with(& self, want_all : bool) -> StringLike rls_setting but honors an explicit see-all request — only a global actor may widen to '*'; everyone else stays at their default scope (a non-global user can never reach '*').
ActiveTenant :: fn tenant_scope_with(& self, want_all : bool) -> TenantScopeTyped form of rls_setting_with
ActiveTenant :: fn is_member_of(& self, tenant_id : Uuid) -> boolWhether the actor is an active member of tenant_id (switch validation).
ActiveTenant :: async fn from_request_parts(parts : & mut Parts, _state : & S) -> Result <Self, Response>

`authz`

ItemWhat it is
pub const LEVEL_SUPERADMIN: i32superadmin — full system, account, and audit control.
pub const LEVEL_ADMIN: i32admin — site, content, and account management.
pub const LEVEL_EDITOR: i32editor — content authoring (custom level, Gate 1.5 Q3).
pub const LEVEL_MEMBER: i32member — limited / read-only access.
fn check_hierarchy(actor_level : i32, required_level : i32) -> boolRoute gating: an actor may use a route requiring required_level when their level is greater than or equal to it (level-80 may reach a level-80 route).
fn can_manage_user(actor_level : i32, target_level : i32) -> boolAccount management: an actor may manage a target only when their level is strictly greater — equal levels cannot manage each other (an admin cannot demote another admin)
pub struct CurrentUserThe authenticated account for the current request, resolved once by crate::middleware::auth::admin_auth and carried in request extensions
CurrentUser :: fn is_superadmin(& self) -> boolWhether this account is the top tier.
pub struct UserPermissionsSet of permission codenames the current user holds
UserPermissions :: fn from(set : std::collections::HashSet <String>) -> Self
UserPermissions :: fn has(& self, code : & str) -> booltrue if the set contains the given codename.
CurrentUser :: async fn from_request_parts(parts : & mut Parts, _state : & S) -> Result <Self, Response>

`repo::rbac`

ItemWhat it is
async fn primary_role_for_user(pool : & PgPool, user_id : Uuid,) -> Result <Option <Role>, sqlx::Error>The user's highest currently-valid role, or None if they hold none
async fn list_roles(pool : & PgPool) -> Result <Vec <Role>, sqlx::Error>Every active, live role, highest level first.
async fn role_by_id(pool : & PgPool, id : Uuid) -> Result <Option <Role>, sqlx::Error>Look up one active role by id.
async fn set_user_role(conn : & mut PgConnection, user_id : Uuid, role_id : Uuid, assigned_by : Option <Uuid>,) -> Result <(), sqlx::Error>Set a user's role: soft-delete every current assignment, then insert the new one as primary

`services::rbac`

ItemWhat it is
async fn resolve_permissions(pool : & PgPool, user_id : Uuid,) -> Result <HashSet <String>, sqlx::Error>Compute the set of permission codenames the given user holds
async fn all_permission_codenames(pool : & PgPool) -> Result <HashSet <String>, sqlx::Error>The full catalog of non-deprecated permission codenames
async fn ensure_bootstrap_admin(pool : & PgPool) -> Result <Option <Uuid>, BootstrapError>Bootstrap an admin user from env vars if users is empty
pub enum BootstrapErrorErrors specific to the bootstrap-seed path

Re-exports. Exported here, defined elsewhere.

ExportDefined in
ActiveTenantactive_tenant::ActiveTenant

Boundary

Reaches into foundation, identity.

Shares tier application with 120 other crates: application-agreements, application-ai, application-analytics, application-approvals, application-assessments, application-audit-log, application-auth, application-billing, … (120 total).

_What this crate deliberately does NOT own is a judgment. No committed registry records one for it, so none is stated here._

Where it sits

Tier (ontology)application
Architectural role (taxonomy)unclassified (baselined)
Locationcrates/application/rbac
Vocabulary in force (lexicon)current

Tier flow. Which tiers this crate's own edges cross.

flowchart LR
  n_application["application"] --> n_foundation["foundation"]
  n_application["application"] --> n_identity["identity"]

Dependencies

Runtime, in this workspace.

CrateTierOptionalOnly on
`application-audit-log`applicationnoalways
`application-core`applicationnoalways
`foundation-audit-log`foundationnoalways
`identity-auth`identitynoalways
`identity-rbac`identitynoalways
`identity-tenant`identitynoalways

Runtime, from outside the workspace.

CrateRequirementFeaturesOptionalOnly on
async-trait^0.1noalways
axum^0.7multipartnoalways
chrono^0.4serdenoalways
serde^1derivenoalways
serde_json^1noalways
sqlx^0.8runtime-tokio, postgres, chrono, uuid, jsonnoalways
thiserror^2noalways
tokio^1fullnoalways
tracing^0.1noalways
uuid^1v4, v7, serde, jsnoalways

Development. None.

Build. None.

Depended on by. 11 workspace crates.

Signal flow — what reaches this crate, and what it reaches.

flowchart LR
  n_application_agreements["application-agreements"] -->|uses| SELF
  n_application_ai["application-ai"] -->|uses| SELF
  n_application_analytics["application-analytics"] -->|uses| SELF
  n_application_auth["application-auth"] -->|uses| SELF
  n_application_cms["application-cms"] -->|uses| SELF
  n_application_contact["application-contact"] -->|uses| SELF
  n_application_dlp["application-dlp"] -->|uses| SELF
  n_application_ledger_accounts["application-ledger-accounts"] -->|uses| SELF
  n_application_ledger_entries["application-ledger-entries"] -->|uses| SELF
  n_application_ledger_reports["application-ledger-reports"] -->|uses| SELF
  n_application_messaging["application-messaging"] -->|uses| SELF
  SELF["application-rbac"]
  SELF -->|runtime| n_application_audit_log["application-audit-log"]
  SELF -->|runtime| n_application_core["application-core"]
  SELF -->|runtime| n_foundation_audit_log["foundation-audit-log"]
  SELF -->|runtime| n_identity_auth["identity-auth"]
  SELF -->|runtime| n_identity_rbac["identity-rbac"]
  SELF -->|runtime| n_identity_tenant["identity-tenant"]
  classDef self fill:#1f883d,stroke:#1f883d,color:#fff;
  class SELF self;

Feature flags

No Cargo features are defined: every capability is unconditional, so no consumer can receive a half-wired crate.

Targets

KindNameSource
libapplication_rbac`src/lib.rs`
testpg_forge_rbac`tests/pg_forge_rbac.rs`

Error model

Error typeNamed by
BootstrapErrorensure_bootstrap_admin

Operational characteristics

PropertyEvidence
async public surfaceyes
async runtimeyes
database accessyes
network I/Oyes
unsafe codenone detected
environment variablesyes

No unsafe block, unsafe fn, unsafe impl or unsafe trait was found by the parser anywhere in this crate's source.

Configuration

VariableRead in
ADMIN_BOOTSTRAP_EMAILsrc/services/rbac.rs
ADMIN_BOOTSTRAP_PASSWORDsrc/services/rbac.rs
CARGO_PKG_VERSIONsrc/lib.rs

11 workspace crates depend on this one: application-agreements, application-ai, application-analytics, application-auth, application-cms, application-contact, application-dlp, application-ledger-accounts, application-ledger-entries, application-ledger-reports, application-messaging.

Verification

KindCount
Unit tests15
Integration tests1
Examples0
Doctests0

Evidence by module. How often each public module is named by something executable.

ModuleTestsExamplesConsumers
crate root100
active_tenant104
authz8011
repo::rbac402
services::rbac405

What the tests establish, by name:

Documentation coverage

MeasureDocumentedTotal
Public items with rustdoc2534
Public modules with a //! block56
pie showData
    title Public items with rustdoc
    "Documented" : 25
    "No rustdoc detected" : 9

Metrics

MetricValue
Rust source files7
Source lines1175
Code lines769
Public API items34
Public modules6
Tests16
Examples0
Cargo features0
Direct runtime dependencies16
Workspace reverse dependencies11
pie showData
    title Public API by kind
    "constant" : 4
    "enum" : 1
    "function" : 9
    "method" : 16
    "struct" : 4
pie showData
    title Rust source composition
    "Code" : 769
    "Blank or comment" : 406

Generation

Rendered by tools-corpus corpus readme from repository evidence alone, renderer schema 2, lexicon current. No model, network service or database was consulted. Regenerate with tools-corpus corpus readme --write; verify with --check.

Todas las application · Manual