Forge module wrapping identity-rbac.
| Tier | application |
| Role | unclassified (baselined) |
| Path | crates/application/rbac |
| Edition | 2024 |
| Targets | application_rbac, pg_forge_rbac |
| Public items | 34 across 6 modules |
| Tests | 16 |
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.sql … 003_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
active_tenantauthzreporepo::rbacservicesservices::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`
| Item | What 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`
| Item | What it is |
|---|---|
pub struct ActiveTenant | The tenant a request operates in, plus the actor's tenant capabilities. |
ActiveTenant :: fn resolve(session_active : Option <Uuid>, memberships : Vec <Uuid>, is_global : bool) -> Self | Resolve the active tenant from the session's stored selection |
ActiveTenant :: fn rls_setting(& self) -> String | The 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) -> TenantScope | The typed scope this actor runs under (Sprint 3.2 D1) |
ActiveTenant :: fn rls_setting_with(& self, want_all : bool) -> String | Like 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) -> TenantScope | Typed form of rls_setting_with |
ActiveTenant :: fn is_member_of(& self, tenant_id : Uuid) -> bool | Whether 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`
| Item | What it is |
|---|---|
pub const LEVEL_SUPERADMIN: i32 | superadmin — full system, account, and audit control. |
pub const LEVEL_ADMIN: i32 | admin — site, content, and account management. |
pub const LEVEL_EDITOR: i32 | editor — content authoring (custom level, Gate 1.5 Q3). |
pub const LEVEL_MEMBER: i32 | member — limited / read-only access. |
fn check_hierarchy(actor_level : i32, required_level : i32) -> bool | Route 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) -> bool | Account 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 CurrentUser | The authenticated account for the current request, resolved once by crate::middleware::auth::admin_auth and carried in request extensions |
CurrentUser :: fn is_superadmin(& self) -> bool | Whether this account is the top tier. |
pub struct UserPermissions | Set of permission codenames the current user holds |
UserPermissions :: fn from(set : std::collections::HashSet <String>) -> Self | — |
UserPermissions :: fn has(& self, code : & str) -> bool | true if the set contains the given codename. |
CurrentUser :: async fn from_request_parts(parts : & mut Parts, _state : & S) -> Result <Self, Response> | — |
`repo::rbac`
| Item | What 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`
| Item | What 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 BootstrapError | Errors specific to the bootstrap-seed path |
Re-exports. Exported here, defined elsewhere.
| Export | Defined in |
|---|---|
ActiveTenant | active_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) |
| Location | crates/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.
| Crate | Tier | Optional | Only on |
|---|---|---|---|
| `application-audit-log` | application | no | always |
| `application-core` | application | no | always |
| `foundation-audit-log` | foundation | no | always |
| `identity-auth` | identity | no | always |
| `identity-rbac` | identity | no | always |
| `identity-tenant` | identity | no | always |
Runtime, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
async-trait | ^0.1 | — | no | always |
axum | ^0.7 | multipart | no | always |
chrono | ^0.4 | serde | no | always |
serde | ^1 | derive | no | always |
serde_json | ^1 | — | no | always |
sqlx | ^0.8 | runtime-tokio, postgres, chrono, uuid, json | no | always |
thiserror | ^2 | — | no | always |
tokio | ^1 | full | no | always |
tracing | ^0.1 | — | no | always |
uuid | ^1 | v4, v7, serde, js | no | always |
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
| Kind | Name | Source |
|---|---|---|
| lib | application_rbac | `src/lib.rs` |
| test | pg_forge_rbac | `tests/pg_forge_rbac.rs` |
Error model
| Error type | Named by |
|---|---|
BootstrapError | ensure_bootstrap_admin |
Operational characteristics
| Property | Evidence |
|---|---|
| async public surface | yes |
| async runtime | yes |
| database access | yes |
| network I/O | yes |
| unsafe code | none detected |
| environment variables | yes |
No unsafe block, unsafe fn, unsafe impl or unsafe trait was found by the parser anywhere in this crate's source.
Configuration
| Variable | Read in |
|---|---|
ADMIN_BOOTSTRAP_EMAIL | src/services/rbac.rs |
ADMIN_BOOTSTRAP_PASSWORD | src/services/rbac.rs |
CARGO_PKG_VERSION | src/lib.rs |
Related capabilities
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
| Kind | Count |
|---|---|
| Unit tests | 15 |
| Integration tests | 1 |
| Examples | 0 |
| Doctests | 0 |
Evidence by module. How often each public module is named by something executable.
| Module | Tests | Examples | Consumers |
|---|---|---|---|
crate root | 1 | 0 | 0 |
active_tenant | 1 | 0 | 4 |
authz | 8 | 0 | 11 |
repo::rbac | 4 | 0 | 2 |
services::rbac | 4 | 0 | 5 |
What the tests establish, by name:
migrate_pending_creates_roles_table—tests/pg_forge_rbac.rsis_member_of_checks_set—src/active_tenant.rsresolve_defaults_primary_when_no_session—src/active_tenant.rsresolve_falls_back_to_primary_when_session_invalid—src/active_tenant.rsresolve_keeps_valid_session_selection—src/active_tenant.rsresolve_none_when_no_memberships—src/active_tenant.rsrls_setting_global_no_tenant_is_star—src/active_tenant.rsrls_setting_nonglobal_no_tenant_is_fail_closed—src/active_tenant.rsrls_setting_scopes_to_active_tenant—src/active_tenant.rsrls_setting_with_global_can_opt_into_star—src/active_tenant.rsrls_setting_with_nonglobal_cannot_reach_star—src/active_tenant.rscan_manage_requires_strictly_greater—src/authz.rshierarchy_admits_equal_and_higher—src/authz.rshierarchy_rejects_lower_and_roleless—src/authz.rsseeded_levels_are_strictly_ordered—src/authz.rstest_module_migrations_non_empty—src/lib.rs
Documentation coverage
| Measure | Documented | Total |
|---|---|---|
| Public items with rustdoc | 25 | 34 |
Public modules with a //! block | 5 | 6 |
pie showData
title Public items with rustdoc
"Documented" : 25
"No rustdoc detected" : 9
Metrics
| Metric | Value |
|---|---|
| Rust source files | 7 |
| Source lines | 1175 |
| Code lines | 769 |
| Public API items | 34 |
| Public modules | 6 |
| Tests | 16 |
| Examples | 0 |
| Cargo features | 0 |
| Direct runtime dependencies | 16 |
| Workspace reverse dependencies | 11 |
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.