Object Management Documentation
Module: Object Registry & Profile Configuration
Version: 1.0
Last Updated: March 12, 2026
📌 Overview
Object Management handles the dynamic entity configuration for Axiom Genesis. It defines:
- What data exists: table/collection mappings, field definitions
- How to access it: field-level policies, masking, projection
- Who can see what: role-based field visibility and row-level access
Core Tables:
app_object_registries- Entity metadata and table mappingsapp_object_profiles- Field-level access policies and configurations
app_object_registries - Entity Registry
Purpose
Maps business entities (CUSTOMERS, ORDERS, PRODUCTS) to actual database tables/collections and stores metadata about fields and relationships.
Schema
SELECT
obj_id (INT), -- Primary Key
obj_tenant_id (INT), -- Tenant isolation
obj_code (VARCHAR(256)), -- Entity identifier (e.g., 'CUSTOMERS')
obj_description (VARCHAR), -- Display name
obj_type_code (VARCHAR), -- Type: 'BUSINESS', 'SYSTEM', 'VIEW'
obj_connection_code (VARCHAR), -- Links to app_connections
obj_payload (JSON), -- Field metadata & definitions
obj_payload_object_name (NVARCHAR), -- Actual table name (e.g., 'app_customers')
obj_payload_identifier (NVARCHAR), -- Primary key field (e.g., 'cus_id')
obj_payload_column_prefix (NVARCHAR), -- Field prefix (e.g., 'cus_')
obj_payload_table (NVARCHAR), -- Source table name
obj_status (INT), -- 1=Active, 0=Inactive
obj_is_deleted (BIT), -- Soft delete flag
obj_audit_data (JSON), -- {created_by, created_at, updated_by, ...}
obj_add_dtls (JSON) -- Additional metadata
FROM app_object_registries
WHERE obj_status = 1 AND obj_is_deleted = 0
obj_payload Structure (JSON Column)
The obj_payload column contains complete field metadata:
{
"object_code": "CUSTOMERS",
"object_name": "Customer Management",
"table_name": "app_customers",
"column_prefix": "cus",
"identifier": "cus_id",
"fields": [
{
"field_code": "cus_id",
"field_name": "Customer ID",
"field_type": "integer",
"is_primary_key": true,
"is_required": false,
"is_read_only": true,
"data_type": "INT",
"description": "Unique customer identifier"
},
{
"field_code": "cus_name",
"field_name": "Customer Name",
"field_type": "string",
"is_required": true,
"is_searchable": true,
"data_type": "VARCHAR(256)",
"validation": {
"min_length": 1,
"max_length": 256
}
},
{
"field_code": "cus_email",
"field_name": "Email Address",
"field_type": "email",
"is_required": true,
"is_searchable": true,
"data_type": "VARCHAR(256)",
"validation": {
"pattern": "^[^@]+@[^@]+\\.[^@]+$"
}
},
{
"field_code": "cus_status",
"field_name": "Status",
"field_type": "select",
"is_required": true,
"data_type": "VARCHAR(50)",
"options": ["ACTIVE", "INACTIVE", "SUSPENDED"],
"default_value": "ACTIVE"
},
{
"field_code": "cus_metadata",
"field_name": "Metadata",
"field_type": "json",
"data_type": "JSON",
"description": "Extensible metadata storage"
}
],
"relationships": [
{
"relationship_code": "CUSTOMER_ORDERS",
"relationship_name": "Customer Orders",
"related_entity": "ORDERS",
"relationship_type": "one_to_many",
"foreign_key": "ord_customer_id"
}
]
}
Middleware Usage Pattern (ReadService)
// From: services/entity/read.service.js
ReadService.get('CUSTOMERS', { status: 'ACTIVE' }, user)
↓
1. Load from Redis: registry:${tenant_id}:CUSTOMERS
2. Cache miss → Query app_object_registries
SELECT obj_payload, obj_payload_identifier, obj_payload_table
FROM app_object_registries
WHERE obj_code = 'CUSTOMERS' AND obj_tenant_id = @tenantId
3. Extract from obj_payload:
- fields[] array for field definitions
- relationships[] for linked entities
- column_prefix ('cus') to auto-detect tenant field ('cus_tenant_id')
4. Use obj_payload_table ('app_customers') as SQL table name
5. Use obj_payload_identifier ('cus_id') as primary key
6. Cache in Redis for 24 hours
// Example: Field detection from payload
const registry = {
obj_payload: {
fields: [
{ field_code: 'cus_id', is_primary_key: true, ... },
{ field_code: 'cus_name', is_required: true, ... },
{ field_code: 'cus_status', field_type: 'select', ... }
]
}
}
// Service extracts field information:
- Required fields: cus_name
- Primary key: cus_id
- Selectable fields: cus_status
Real Example: CUSTOMERS Entity
{
"obj_id": 101,
"obj_tenant_id": 1,
"obj_code": "CUSTOMERS",
"obj_description": "Customer Management",
"obj_type_code": "BUSINESS",
"obj_connection_code": "DEFAULT_TENANT_DB",
"obj_payload_object_name": "app_customers",
"obj_payload_identifier": "cus_id",
"obj_payload_column_prefix": "cus",
"obj_payload_table": "app_customers",
"obj_status": 1,
"obj_is_deleted": 0,
"obj_payload": {
"object_code": "CUSTOMERS",
"object_name": "Customer Management",
"table_name": "app_customers",
"column_prefix": "cus",
"identifier": "cus_id",
"fields": [
{
"field_code": "cus_id",
"field_name": "Customer ID",
"field_type": "integer",
"is_primary_key": true,
"is_read_only": true,
"data_type": "INT"
},
{
"field_code": "cus_name",
"field_name": "Customer Name",
"field_type": "string",
"is_required": true,
"data_type": "VARCHAR(256)"
},
{
"field_code": "cus_email",
"field_name": "Email",
"field_type": "email",
"is_required": true,
"data_type": "VARCHAR(256)"
},
{
"field_code": "cus_status",
"field_name": "Status",
"field_type": "select",
"data_type": "VARCHAR(50)",
"options": ["ACTIVE", "INACTIVE"]
}
]
},
"obj_audit_data": {
"created_by": "admin",
"created_at": "2026-01-15T08:00:00Z",
"updated_by": "admin",
"updated_at": "2026-03-10T14:30:00Z"
}
}
app_object_profiles - Field-Level Policies
Purpose
Defines how fields are accessed, including visibility, masking, and write permissions. Profiles are applied per user/role to control:
- Projection: Which fields to include/exclude
- Masking: How to obfuscate sensitive fields (email, phone, SSN)
- Row Filters: Which records user can access (department = @user.department)
- Write Control: Which fields are editable
Schema
SELECT
prf_id (INT), -- Primary Key
prf_tenant_id (INT), -- Tenant isolation
prf_code (VARCHAR(256)), -- Profile identifier
prf_name (VARCHAR), -- Display name
prf_object_code (VARCHAR), -- Links to app_object_registries
prf_type (VARCHAR), -- 'view', 'edit', 'admin'
prf_policy (JSON), -- Access control policy
prf_is_default (BIT), -- 1=Use for all users, 0=Specific to role/user
prf_config (JSON), -- Additional configuration
prf_status (INT), -- 1=Active, 0=Inactive
prf_is_deleted (BIT), -- Soft delete flag
prf_audit_data (JSON), -- Audit trail
prf_add_dtls (JSON) -- Additional metadata
FROM app_object_profiles
WHERE prf_status = 1 AND prf_is_deleted = 0
prf_policy Structure (JSON Column) - Complete Reference
{
"tenantPolicy": "auto", // 'auto' (filter by user tenant) | 'manual'
"projection": {
// Field visibility control
"type": "include", // 'include' all specified | 'exclude' specified
"fields": ["cus_id", "cus_name", "cus_email", "cus_status"],
"excludeFields": [
// Fields NEVER returned
"cus_ssn",
"cus_payment_card",
"cus_internal_notes"
]
},
"masking": {
// Field-level masking/obfuscation
"cus_email": "email_mask", // email → ****@****
"cus_phone": "phone_mask", // phone → ***-****-1234
"cus_ssn": "ssn_mask", // SSN → ***-**-1234
"cus_credit_card": "card_mask", // Card → ****-****-****-1234
"cus_date_of_birth": "date_mask" // DOB → masked or hidden
},
"rowFilters": {
// Row-level access control
"department": "@user.department", // Only show records matching user's dept
"region": "@user.region", // Only show records matching user's region
"status": ["ACTIVE", "PENDING"] // Only show in these statuses
},
"writableFields": [
// Fields user can edit
"cus_name",
"cus_email",
"cus_status"
],
"readOnlyFields": [
// Fields user can read but not edit
"cus_id",
"cus_created_date",
"cus_tenant_id"
],
"conditionalRules": [
// Dynamic rules based on context
{
"condition": "role === 'MANAGER'",
"action": "can_edit_all",
"fields": ["cus_name", "cus_email", "cus_status", "cus_credit_limit"]
},
{
"condition": "role === 'USER'",
"action": "can_edit",
"fields": ["cus_email", "cus_phone"]
}
],
"computedFields": [
// Derived fields (not stored in DB)
{
"field_code": "cus_full_info",
"expression": "`${cus_name} (${cus_email})`"
}
]
}
Middleware Usage Pattern (WriteService/ReadService)
// From: services/entity/write.service.js and services/entity/read.service.js
// Step 1: Load profile
const profile = await getProfile('CUSTOMERS', user.tenant_id);
// Query: SELECT prf_policy FROM app_object_profiles
// WHERE prf_object_code = 'CUSTOMERS' AND prf_tenant_id = @tenantId
// Step 2: Parse policy
const policy = JSON.parse(profile.prf_policy);
// Step 3: Apply policy to request
// FOR READS:
const results = await db.query(...);
results = results.map(record => {
// Remove excluded fields
policy.projection.excludeFields.forEach(field => {
delete record[field];
});
// Apply masking
Object.entries(policy.masking).forEach(([field, maskType]) => {
if (record[field]) {
record[field] = applyMask(record[field], maskType);
}
});
// Keep only allowed fields (include/exclude)
if (policy.projection.type === 'include') {
const allowed = policy.projection.fields;
Object.keys(record).forEach(field => {
if (!allowed.includes(field)) delete record[field];
});
}
return record;
});
// FOR WRITES:
const payload = {
cus_name: 'New Name',
cus_email: 'email@example.com',
cus_ssn: '123-45-6789' // ← User trying to edit SSN
};
// Filter to only writable fields
const filtered = {};
policy.writableFields.forEach(field => {
if (field in payload) {
filtered[field] = payload[field];
}
});
// Result: { cus_name, cus_email } (cus_ssn removed)
// Step 4: Apply row filters (WHERE clause)
// WHERE department = @user.department AND status IN ('ACTIVE', 'PENDING')
Real Example: CUSTOMERS Profile (Sales Rep Role)
{
"prf_id": 201,
"prf_tenant_id": 1,
"prf_code": "CUSTOMERS_SALES_REP",
"prf_name": "Customer - Sales Rep View",
"prf_object_code": "CUSTOMERS",
"prf_type": "edit",
"prf_is_default": 0,
"prf_status": 1,
"prf_policy": {
"tenantPolicy": "auto",
"projection": {
"type": "include",
"fields": [
"cus_id",
"cus_name",
"cus_email",
"cus_phone",
"cus_status",
"cus_credit_limit",
"cus_sales_rep"
],
"excludeFields": ["cus_ssn", "cus_internal_notes", "cus_cost_price"]
},
"masking": {
"cus_phone": "phone_mask",
"cus_email": "email_mask"
},
"rowFilters": {
"cus_sales_rep": "@user.id" // Only see own customers
},
"writableFields": ["cus_email", "cus_phone", "cus_status"],
"readOnlyFields": ["cus_id", "cus_credit_limit", "cus_sales_rep"]
}
}
Real Example: CUSTOMERS Profile (Admin Role)
{
"prf_id": 202,
"prf_tenant_id": 1,
"prf_code": "CUSTOMERS_ADMIN",
"prf_name": "Customer - Admin View",
"prf_object_code": "CUSTOMERS",
"prf_type": "admin",
"prf_is_default": 0,
"prf_status": 1,
"prf_policy": {
"tenantPolicy": "auto",
"projection": {
"type": "include",
"fields": [
"cus_id",
"cus_name",
"cus_email",
"cus_phone",
"cus_ssn",
"cus_status",
"cus_credit_limit",
"cus_sales_rep",
"cus_internal_notes"
]
},
"masking": {}, // No masking for admins
"rowFilters": {}, // Can see all records
"writableFields": [
// Can edit all fields
"cus_id",
"cus_name",
"cus_email",
"cus_phone",
"cus_ssn",
"cus_status",
"cus_credit_limit",
"cus_sales_rep",
"cus_internal_notes"
]
}
}
Masking Types Reference
Available masking functions applied in prf_policy.masking:
| Mask Type | Input Example | Output | Use Case |
|---|---|---|---|
email_mask | john.doe@example.com | j***@**** | Email privacy |
phone_mask | 555-123-4567 | *-**-4567 | Phone privacy |
ssn_mask | 123-45-6789 | *--6789 | Social security numbers |
card_mask | 4532-1234-5678-9999 | **-**-****-9999 | Credit cards |
general_mask | anyvalue | masked | Generic masking |
date_mask | 1990-05-15 | [REDACTED] | Date of birth/sensitive dates |
full_hide | value | [HIDDEN] | Completely hide value |
Querying Object Registries & Profiles
Get Entity Configuration for Editing
-- Fetch complete CUSTOMERS entity definition
SELECT
obj_code,
obj_description,
obj_payload,
obj_payload_identifier,
obj_payload_column_prefix
FROM app_object_registries
WHERE obj_code = 'CUSTOMERS'
AND obj_tenant_id = 1
AND obj_is_deleted = 0
AND obj_status = 1;
-- Extract fields from obj_payload JSON
SELECT
obj_code,
JSON_VALUE(obj_payload, '$.fields[0].field_code') AS first_field,
JSON_QUERY(obj_payload, '$.fields') AS all_fields
FROM app_object_registries
WHERE obj_code = 'CUSTOMERS';
Get User's Effective Profile
-- Find the profile applicable to user's role
SELECT
prf_code,
prf_type,
prf_policy
FROM app_object_profiles
WHERE prf_object_code = 'CUSTOMERS'
AND prf_tenant_id = 1
AND (
prf_is_default = 1
OR prf_code = (
-- Get role-specific profile
SELECT profile_code FROM user_roles
WHERE user_id = 'user123' AND role_id = 2
)
)
AND prf_is_deleted = 0
AND prf_status = 1;
List All Entities and Their Profiles
SELECT
r.obj_code,
r.obj_description,
COUNT(p.prf_id) as profile_count,
STRING_AGG(p.prf_type, ', ') as profile_types
FROM app_object_registries r
LEFT JOIN app_object_profiles p ON r.obj_code = p.prf_object_code
AND r.obj_tenant_id = p.prf_tenant_id
WHERE r.obj_tenant_id = 1
AND r.obj_is_deleted = 0
AND r.obj_status = 1
GROUP BY r.obj_code, r.obj_description
ORDER BY r.obj_code;
API Examples
Read CUSTOMERS (With Profile Applied)
GET /api/v1/customers?status=ACTIVE
Headers: {
'x-tenant-id': 'encrypted_value',
'Authorization': 'Bearer token'
}
Response:
{
"success": true,
"data": [
{
"cus_id": 1,
"cus_name": "Acme Corp",
"cus_email": "j****@****", // Masked per profile
"cus_status": "ACTIVE"
// cus_ssn not included (excluded per profile.projection)
// cus_internal_notes not included (admin-only field)
}
]
}
Create CUSTOMER
POST /api/v1/customers
Headers: {
'x-tenant-id': 'encrypted_value',
'Authorization': 'Bearer token'
}
Body: {
"cus_name": "New Company",
"cus_email": "contact@newco.com",
"cus_status": "ACTIVE"
// Cannot include cus_ssn (not in writableFields per profile)
}
Response:
{
"success": true,
"data": {
"cus_id": 102,
"cus_name": "New Company",
"cus_email": "contact@newco.com",
"cus_status": "ACTIVE",
"cus_tenant_id": 1,
"cus_audit_data": {
"created_by": "user123",
"created_at": "2026-03-12T10:30:00Z"
}
}
}
Caching Strategy
Registry Caching
Cache Key: registry:${tenantId}:${objectCode}
TTL: 24 hours
Example: registry:1:CUSTOMERS
Invalidation Triggers:
- Update to app_object_registries → clear registry:1:CUSTOMERS
- Update to obj_payload → clear registry:1:CUSTOMERS
- Automatic on INSERT/UPDATE/DELETE
Profile Caching
Cache Key: profile:${tenantId}:${objectCode}
TTL: 24 hours
Example: profile:1:CUSTOMERS
Invalidation Triggers:
- Update to app_object_profiles → clear profile:1:CUSTOMERS
- Update to prf_policy → clear profile:1:CUSTOMERS
Do's and Don'ts
✅ DO
- Define all fields in obj_payload.fields[] - Service layer needs complete field definitions
- Use column_prefix consistently - Prefix must match table column naming (cus*, ord*, etc.)
- Apply masking for sensitive fields - Always mask SSN, payment info, DoB in profiles
- Test profiles by role - Verify each role's projection works as expected
- Use row filters for tenant isolation - Auto-apply tenant constraints in policy
- Cache registries and profiles - Expected 24h TTL, automatic invalidation on update
- Document relationships in obj_payload - Include relationship_code and related_entity references
- Version your profiles - Keep multiple profiles per entity for different roles/views
❌ DON'T
- Hardcode field names in service code - Always read from obj_payload.fields[]
- Forget to apply masking - Policy.masking should be applied before returning data
- Use prf_policy in stored procedures - Policies applied at middleware service layer only
- Create profiles without exclusions - Always explicitly exclude sensitive fields
- Skip row-level filters - Even if all records visible, document why policy.rowFilters is empty
- Cache profile longer than 24h - Risk stale policy being applied to requests
- Modify obj_payload directly in code - Use configuration APIs to update metadata
- Assume projection auto-applies - Service layer must explicitly implement projection logic
Common Issues & Solutions
| Issue | Cause | Solution |
|---|---|---|
| Sensitive field visible | Projection doesn't exclude field | Add to prf_policy.projection.excludeFields |
| Masking not applied | prf_policy.masking entry missing | Add masking rule for field in profile |
| User sees all records | Row filter missing | Add rowFilters constraint in policy |
| Write includes read-only | writableFields not filtered | Apply writableFields filter in write service |
| Wrong table queried | obj_payload_table incorrect | Verify obj_payload_table value matches actual table |
| Prefix mismatch error | column_prefix ≠ actual columns | Update obj_payload_column_prefix to match table |
Related Documentation
- PAGE_CONFIGURATION.md - How profiles integrate with form validation
- DATABASE.md - Core database schema reference
- DATAVIEW_CONFIGURATION.md - How objects display in dataviews
- MIDDLEWARE.md - Service layer implementation details