Operator documentation
Lifecycle Architecture Document (LIFECYCLE.md)
1. **Hierarchy is Absolute**:
Lifecycle Architecture Document (LIFECYCLE.md)
System Invariants (Architectural Law)
- Hierarchy is Absolute:
- Owner (Platform Admin, singleton) → Director (×∞) → ISP/Tenant (×∞) → Partner (×∞, recursive, no depth limit) → Employee (×∞).
- No entity can access data outside its subtree (no sibling access, no upstream access).
- No RBAC Above ISP:
- Owner, Directors, and ISPs are organizational entities with hard-coded, hierarchy-based access.
- RBAC (Roles & Permissions) exists ONLY within the ISP Tenant Zone (for Employees and Partners).
- Ownership is Explicit:
- Every object has a
tenant_id(belongs to) and tenants haveparent_id(managed by).
- Every object has a
- Tenant Isolation:
- Wallets, Subscribers, Plans, and Logs exist strictly within the ISP Tenant boundary.
- No Partner Depth Limit:
- Partners can create sub-partners, who can create sub-sub-partners, infinitely.
- Each partner can also create their own employees, unlimited.
A. Full Hierarchy
Owner (singleton, platform admin)
└── Director ×∞ (user_type=1, no tenant_id)
└── ISP/Tenant ×∞ (type=isp, user_type=2 for ISP admin)
├── Employee ×∞ (user_type=2, tenant_id=ISP)
└── Partner ×∞ (user_type=3, type=partner, recursive)
├── Employee ×∞ (user_type=2, tenant_id=partner's tenant)
└── Sub-Partner ×∞ (user_type=3, type=partner)
├── Employee ×∞
└── Sub-Sub-Partner ×∞
└── ... (no depth limit)
User Types
| user_type | Role | Description |
|---|---|---|
| 0 / NULL | Owner | Platform admin (singleton). No tenant_id. |
| 1 | Director | Regional manager. No tenant_id. Has own tenant (type=director). |
| 2 | Employee | Staff with RBAC. Has tenant_id (belongs to ISP or Partner). |
| 3 | Partner | Reseller admin. Has tenant_id (own partner tenant). |
Tenant Types
| type | Description | parent_id |
|---|---|---|
| root | Platform root (NowaCRM) | NULL |
| director | Director's org node | root tenant ID |
| isp | ISP/Tenant boundary | director's tenant ID |
| partner | Partner org node (recursive) | ISP or parent partner's tenant ID |
B. Domain Routing
| Domain | Who | Purpose |
|---|---|---|
admin.nowacrm.test |
Owner | Create/manage Directors, system settings |
app.nowacrm.test/director/* |
Directors | Create/manage ISPs under them |
app.nowacrm.test/* |
ISP + Partners + Employees | Unified portal with hierarchical org selector |
login.nowacrm.test (future) |
End users/subscribers | Customer self-service portal |
Auth Guards
| Guard | Portal | Validates |
|---|---|---|
owner |
admin.* | Owner role via user_roles table |
director |
app.*/director | user_type = 1 |
app |
app.* | user_type in [2, 3] |
C. Entity Lifecycle
1. Owner (System Bootstrap)
- Created by: Database Seeder / CLI Command.
- Cardinality: Singleton (only one).
- Scope: Global — all Directors, Audit Logs, System Settings.
- Cannot be deleted or suspended via UI.
2. Director
- Created by: Owner.
- Quantity: Unlimited per platform.
- What's created:
tenantsrow:type=director,parent_id=root_tenant.usersrow:user_type=1,tenant_id=NULL(directors don't belong to a tenant scope).
- Scope: Can only see/manage ISPs they created. Cannot see other Directors.
3. ISP / Tenant (Tenant Boundary)
- Created by: Director.
- Quantity: Unlimited per Director.
- What's created:
tenantsrow:type=isp,parent_id=director's tenant,director_id=director_user_id.usersrow: ISP Admin withuser_type=2,tenant_id=new_isp_tenant_id.isp_profilesrow: Business/legal info.- Default RBAC roles (Admin, Manager, Support) scoped to this tenant.
- Wallet initialization.
- This is the tenant boundary — all data below is scoped by
tenant_id.
4. Partner (Recursive, No Depth Limit)
- Created by: ISP Admin, or any Parent Partner.
- Quantity: Unlimited. Sub-partners unlimited. No depth limit.
- What's created:
tenantsrow:type=partner,parent_id=creator's tenant.usersrow: Partner Admin withuser_type=3,tenant_id=new_partner_tenant_id.partnersrow: Business profile (commission, contact info).
- Scope: Own subtree only — own employees, own sub-partners, and everything below.
- Cannot see sibling partners, parent ISP config, or upstream data.
5. Employee
- Created by: ISP Admin, or Partner Admin.
- Quantity: Unlimited per ISP or Partner.
- What's created:
usersrow:user_type=2,tenant_id=the_org_they_belong_to.
- RBAC: Must be assigned a Role (defined by root ISP).
- Scope: Determined by their org placement + RBAC permissions.
D. Creation Lifecycle (Who Creates Whom)
| Creator | Can Create | Portal |
|---|---|---|
| Owner | Director (×∞) | admin.nowacrm.test |
| Director | ISP/Tenant (×∞) | app.nowacrm.test/director |
| ISP Admin | Partner (×∞), Employee (×∞) | app.nowacrm.test |
| Partner | Sub-Partner (×∞, no depth limit), Employee (×∞) | app.nowacrm.test |
| Employee | Nothing (consumer of RBAC permissions) | app.nowacrm.test |
Creation Flow
Owner logs into admin.nowacrm.test
→ Creates Director "Galaxy Telecom"
→ Director user (user_type=1) + Director tenant (type=director)
Director logs into app.nowacrm.test/director
→ Creates ISP "Nova Internet Services"
→ ISP tenant (type=isp, parent=director tenant)
→ ISP Admin user (user_type=2, tenant_id=ISP)
→ ISP Profile, Wallet, Default Roles
ISP Admin logs into app.nowacrm.test
→ Creates Partner "CityNet Resellers"
→ Partner tenant (type=partner, parent=ISP tenant)
→ Partner Admin user (user_type=3, tenant_id=partner)
→ Partner profile (commission, business info)
→ Creates Employee "John Support"
→ User (user_type=2, tenant_id=ISP) + Role assignment
Partner Admin logs into app.nowacrm.test
→ Creates Sub-Partner "LocalNet"
→ Partner tenant (type=partner, parent=CityNet's tenant)
→ Sub-Partner Admin user (user_type=3)
→ Creates Employee "Jane Sales"
→ User (user_type=2, tenant_id=CityNet's tenant)
Sub-Partner creates Sub-Sub-Partner... (infinite depth)
E. Scope Boundaries (Visibility)
| Rule | Description |
|---|---|
| Upstream: None | Partner cannot see ISP config. Sub-partner cannot see parent partner's data. |
| Downstream: Full | ISP sees everything below. Partner sees their full subtree. |
| Sibling: Zero | Partner A cannot see Partner B. Director A cannot see Director B. |
ISP Portal Org Selector
The unified ISP portal includes a hierarchical org selector in the header:
- ISP Admin sees: All (ISP + all partners + all sub-partners below)
- Partner Admin sees: Own org + all sub-partners below
- Employee at leaf org: No selector (only their own org data)
Switching the org selector re-scopes all read queries to WHERE tenant_id IN (selected_subtree).
Write operations always use the user's own tenant_id for safety.
F. Tenant Hierarchy (Database)
hierarchy_path Format
Each tenant stores a materialized path: /{root_id}/{parent_id}/.../{self_id}/
Example:
/1/ → Root (NowaCRM Platform)
/1/2/ → Director (Galaxy Telecom)
/1/2/14/ → ISP (Nova Internet) under Galaxy
/1/2/14/20/ → Partner (CityNet) under Nova
/1/2/14/20/25/ → Sub-Partner (LocalNet) under CityNet
Subtree Query
To get all descendants of tenant X:
SELECT id FROM tenants WHERE hierarchy_path LIKE '{X.hierarchy_path}%'
G. Tenant Boundary Data
Global Data (Shared)
- System updates, platform settings.
- Global hardware types (ONU Models) defined by Owner.
ISP Tenant Data (Isolated)
- Plans & Pricing: Unique to each ISP.
- Subscribers: Strictly siloed per ISP.
- Wallet Transactions: Never cross ISP boundaries.
- RBAC Roles: 'Support' in ISP A ≠ 'Support' in ISP B.
- Audit Logs: Tenant-scoped.
Partner Data (Sub-Tenant)
- Subset of ISP data.
- Partners do not define Plans (they sell ISP plans).
- Partners do not define Roles (they use ISP-defined roles).
- Partners have their own Wallet (commission tracking).
G2. Plan Sharing Lifecycle
Share Types
- Commission (%): Partner earns X% of plan price per renewal. ISP revenue = price × (1 - rate/100). Rate: 0.01% – 99.99%.
- Fixed Price (₹): Partner pays fixed cost to ISP per renewal. Partner earning = price - cost. Cost must be > 0 and ≤ plan price.
Rules & Invariants
sell_priceis always plan.price (not configurable).- One share per plan+partner pair (unique constraint). Soft-deleted shares are restored on re-assignment.
commission_rate >= 100is rejected (ISP must retain revenue).buy_price = 0is rejected (ISP must earn something).buy_price > plan.priceis rejected (partner cannot pay more than MRP).- Opposite-type fields are auto-cleared: commission sets buy_price=null, fixed sets commission_rate=null.
Renewal Flow
SubscriberBillingService::resolvePartnerShare()finds active share for plan+partner.- If commission:
partner_cost = plan.price × (1 - rate/100). - If fixed:
partner_cost = buy_price. - If no share exists: partner pays full MRP (
plan.price). - GST applied on partner_cost (always tax-exclusive).
- Total debited from partner wallet → invoice generated.
Cascading Effects
- Deactivating share → next renewal falls back to MRP.
- Changing commission rate → applies from next renewal (existing invoices unaffected).
- Plan price change → commission recalculates automatically; fixed amount stays constant.
- Bulk assign → applies same rate/cost to multiple plans for one partner in a single operation.
H. RBAC Lifecycle (ISP Level Only)
Context: Applies only to Employees of ISPs and Partners.
1. Role Creation
- Defined by ISP Admin (root tenant of the ISP).
- Stored with
tenant_id= ISP tenant ID. - Examples:
ISP Admin,Field Technician,L1 Support,Partner Manager.
2. Permission Assignment
- Granular permissions (e.g.,
subscriber.create,wallet.topup). - Assigned to Roles, not Users directly.
3. Effective Permission Resolution
- User logs in → System loads Roles via
user_roles→ loads Permissions viarole_permissions. - Constraint: Even with
subscriber.deletepermission, user can ONLY delete subscribers within their org subtree. - Algorithm:
HasPermission(Action) AND InSubtree(ResourceTenantID)
I. Deletion / Suspension Flow
1. Director Removal
- Action: Archive / Soft Delete.
- Impact: Orphans associated ISPs.
- Resolution: ISPs must be re-assigned to Owner or another Director before deletion.
2. ISP Suspension
- Action: Status =
suspended. - Cascade:
- All ISP Employees locked out.
- All Partners (and their sub-partners, recursively) locked out.
- RADIUS/NAS executes CoA to disconnect all subscribers.
- Billing cycles pause.
3. Partner Suspension
- Action: Status =
suspended. - Cascade:
- All sub-partners (recursively) suspended.
- All employees under this partner locked out.
- Subscribers re-assigned or frozen.
- Wallet balance frozen.
4. Employee Removal
- Action: Deactivate (status =
inactive). - Impact: Loses login access immediately. No cascade.
J. Invariants (Must Never Break)
- Owner is a singleton, immutable, cannot be deleted.
- No Cross-Tenant Data Leakage: Every query for tenant data MUST include tenant scoping.
- No Circular Hierarchies: A tenant cannot be its own ancestor (
hierarchy_pathenforces this). - No Partner Depth Limit: The recursive partner hierarchy has no artificial depth cap.
- RBAC Containment: A Role created by ISP A cannot be assigned to a user in ISP B.
- Write Safety: Write operations always use the authenticated user's
tenant_id, never the "viewing" subtree. - Suspension Cascades Downward: Suspending any node locks out the entire subtree below it.