Automated Org Intelligence: BridgeQL's Five-Dimensional Audit Engine
Most Salesforce audits check one thing at a time. BridgeQL's audit engine checks five dimensions simultaneously, correlates findings across them, and generates prioritized recommendations sorted by effort and impact.
Why Five Dimensions
A Salesforce org is not a database. It is a system with interacting layers: data, schema, security, compliance, and performance. A finding in one layer almost always has implications in others. 500 records with null required fields (data quality) is also a compliance risk if those fields are needed for regulatory reporting. A wide object with 400 custom fields (schema) is also a performance issue and a security surface area problem.
Single-dimension audits miss these correlations. BridgeQL's audit engine (in the bridgeql-assess crate) runs all five dimensions, scores them independently, then cross-correlates findings to surface systemic issues.
The Type System
Before diving into dimensions, here is the type system that underlies the entire audit engine. Every audit, regardless of dimension, produces the same data structures:
// From bridgeql-assess/src/types.rs
pub enum AuditType {
Schema,
DataQuality,
Security,
Automation, // Part of Schema dimension
Performance,
Compliance,
Full, // Run all dimensions
}
pub enum Severity {
Info, // Informational, no action needed
Warning, // Should be addressed, not urgent
Critical, // Must be addressed before migration/go-live
}
pub struct AuditResult {
pub timestamp: String,
pub org_id: String,
pub org_alias: String,
pub audit_type: Option<AuditType>,
pub summary: AuditSummary,
pub findings: Vec<Finding>,
pub recommendations: Vec<Recommendation>,
}
pub struct AuditSummary {
pub score: u32, // 0-100 health score
pub critical_count: u32,
pub warning_count: u32,
pub info_count: u32,
pub total_checks: u32,
pub items_checked: u32,
pub duration_ms: u64,
pub categories: HashMap<String, u32>,
pub modules_run: Vec<String>,
}
pub struct Finding {
pub id: String, // e.g., "REQ-Account-Industry"
pub severity: Severity,
pub category: String, // e.g., "Required Field"
pub title: String,
pub description: String,
pub object: Option<String>,
pub field: Option<String>,
pub count: Option<u64>,
pub sample_ids: Vec<String>,
pub remediation: String,
}
pub struct Recommendation {
pub priority: u32, // 1 = highest
pub title: String,
pub description: String,
pub effort: String, // "Low", "Medium", "High"
pub impact: String, // "Low", "Medium", "High"
pub related_findings: Vec<String>,
}
This is not an accident. The uniform structure means any finding from any dimension can be cross-correlated with findings from other dimensions. A Finding has an optional object and field reference, which is the join key for correlation.
Dimension 1: Data Quality
Data quality checks run live SOQL queries against the org. Four categories: field completeness, picklist hygiene, duplicate detection, and orphan records.
// From bridgeql-assess/src/audit.rs
pub fn check_required_fields(&self)
-> Result<(Vec<Finding>, u32)>
{
let mut findings = Vec::new();
let objects_to_check =
vec!["Account", "Contact", "Lead",
"Opportunity", "Case"];
for obj_name in objects_to_check {
if let Some(schema) = self.schemas.get(obj_name) {
let required_fields: Vec<&FieldSchema> =
schema.fields.iter()
.filter(|f| f.required
&& f.field_type != "id")
.collect();
for field in &required_fields {
let query = format!(
"SELECT COUNT(Id) FROM {} \
WHERE {} = null",
obj_name, field.name
);
if let Ok(count) =
self.run_count_query(&query) {
if count > 0 {
let severity = if count > 1000 {
Severity::Critical
} else if count > 100 {
Severity::Warning
} else {
Severity::Info
};
findings.push(Finding {
id: format!(
"REQ-{}-{}", obj_name,
field.name),
severity,
category:
"Required Field".to_string(),
title: format!(
"{}.{} has {} null values",
obj_name, field.name, count),
// ... remediation, description
});
}
}
}
}
}
Ok((findings, items_checked))
}
Picklist hygiene uses a similar approach: for each picklist field, query for records where the value is not in the current active picklist definition. This catches records that were created before picklist values were restricted, or records imported with non-standard values.
// Duplicate detection via aggregate GROUP BY
let query = "SELECT Email, COUNT(Id) cnt \
FROM Contact \
WHERE Email != null \
GROUP BY Email \
HAVING COUNT(Id) > 1";
// Each row in the result is a duplicate group.
// 150 rows = 150 email addresses that appear on
// multiple Contact records.
Orphan detection checks child records with populated lookup fields that point to deleted parents. This is a SOQL anti-join pattern: find Contacts where AccountId is not null but the Account does not exist. Salesforce does not allow true orphans on master-detail relationships, but lookup relationships can have them when parent records are deleted without cascading.
Dimension 2: Security
Security audits analyze permissions, field-level security gaps, sharing rule complexity, and dormant users. These checks use the Tooling API and metadata queries.
// Security audit checks
// 1. Permission set proliferation
SELECT Id, Name, Label, IsCustom,
(SELECT Id FROM Assignments)
FROM PermissionSet
WHERE IsCustom = true
// 2. Field-level security gaps
// Fields accessible to profiles that shouldn't see them
SELECT SobjectType, Field, PermissionsEdit,
PermissionsRead, Parent.Profile.Name
FROM FieldPermissions
WHERE SobjectType IN ('Account', 'Contact', 'Opportunity')
AND PermissionsRead = true
// 3. Sharing rule complexity
SELECT Id, DeveloperName, SObjectType
FROM SharingRule
// 4. Dormant users (no login in 90+ days)
SELECT Id, Name, LastLoginDate, IsActive, Profile.Name
FROM User
WHERE IsActive = true
AND LastLoginDate < LAST_N_DAYS:90
The security dimension also includes PII detection. BridgeQL scans field names and descriptions for patterns that indicate personally identifiable information:
pub enum SensitivityType {
Pii, // SSN, email, phone, address
Phi, // Medical record, diagnosis, prescription
Financial, // Credit card, bank account, salary
Credential, // Password, token, API key
Other,
}
// Pattern-based PII detection
fn detect_pii(field: &FieldSchema) -> Option<SensitivityType> {
let name = field.name.to_lowercase();
let label = field.label.to_lowercase();
// SSN patterns
if name.contains("ssn") || name.contains("social_security")
|| label.contains("social security") {
return Some(SensitivityType::Pii);
}
// Financial patterns
if name.contains("credit_card") || name.contains("bank_account")
|| name.contains("routing_number")
|| label.contains("salary") {
return Some(SensitivityType::Financial);
}
// Health patterns
if name.contains("diagnosis") || name.contains("prescription")
|| name.contains("medical_record") {
return Some(SensitivityType::Phi);
}
// Credential patterns
if name.contains("password") || name.contains("api_key")
|| name.contains("secret") || name.contains("token") {
return Some(SensitivityType::Credential);
}
None
}
Every PII finding includes the field, the sensitivity type, and a remediation recommendation. A field named SSN__c on Account without Platform Encryption generates a Critical finding with a remediation to enable Shield Platform Encryption.
Dimension 3: Compliance
Compliance checks focus on audit trail coverage, PII handling, and data retention. The key questions: Is field history tracking enabled on fields that regulators require? Are PII fields properly protected? Is there a data retention policy, and are records being retained or purged according to it?
// Compliance checks
// Field history tracking coverage
SELECT EntityDefinitionId, EntityDefinition.QualifiedApiName,
FieldDefinitionId, FieldDefinition.QualifiedApiName
FROM FieldHistoryConfig
WHERE IsTracked = true
// Compare against PII fields to find gaps
// If SSN__c exists but is NOT in FieldHistoryConfig,
// that is a compliance finding.
// Data retention analysis
SELECT SobjectType, COUNT(Id) total,
MIN(CreatedDate) oldest,
MAX(CreatedDate) newest
FROM (various objects)
// Records older than retention policy = finding
The compliance dimension cross-references with the security dimension. If a PII field (detected in security audit) does not have field history tracking (detected in compliance audit), that is a cross-dimension finding with Critical severity. Neither dimension alone would surface this.
Dimension 4: Schema
Schema analysis looks at structural health: field usage rates, relationship complexity, automation inventory, and naming convention adherence.
// Schema complexity scoring
// From bridgeql-assess/src/scoring.rs
fn score_schema(custom_objects: usize) -> u32 {
match custom_objects {
0..=5 => 2,
6..=20 => 4,
21..=50 => 6,
51..=100 => 8,
_ => 10,
}
}
fn score_code(
apex_classes: usize, apex_triggers: usize
) -> u32 {
let total = apex_classes + apex_triggers;
match total {
0..=10 => 2,
11..=50 => 4,
51..=100 => 6,
101..=200 => 8,
_ => 10,
}
}
// Weighted total across dimensions
fn calculate_weighted_total(
volume: u32, schema: u32, code: u32,
integration: u32, quality: u32, relationship: u32,
) -> u32 {
let weighted =
(volume as f64 * 0.20 * 10.0) // 20% weight
+ (schema as f64 * 0.25 * 10.0) // 25% weight
+ (code as f64 * 0.20 * 10.0) // 20% weight
+ (integration as f64 * 0.15 * 10.0) // 15% weight
+ (quality as f64 * 0.10 * 10.0) // 10% weight
+ (relationship as f64 * 0.10 * 10.0); // 10% weight
weighted.round() as u32
}
The schema dimension also inventories all automation: Apex triggers, Flows, Process Builders, Workflow Rules, Validation Rules, and Approval Processes. Each automation is classified by object and trigger event, and potential conflicts are detected:
pub struct AutomationConflict {
pub object: String,
pub trigger_event: String,
pub automations: Vec<String>,
pub risk_level: Severity,
pub description: String,
}
// Conflict example:
// Object: Opportunity
// Event: after update
// Automations:
// - OpportunityTrigger (Apex)
// - Update Stage Flow (Flow)
// - Close Date Workflow (Workflow Rule)
// Risk: High - three automations fire on the same event,
// execution order is non-deterministic
Three automations on the same object and trigger event is a Warning. Five or more is Critical. I have seen orgs with 11 automations on Account after update. The execution order is non-deterministic between Flows and triggers. That is a production incident waiting to happen.
Dimension 5: Performance
Performance checks identify large data volumes (LDV), wide objects, storage consumption, and cascade trigger risks.
// Performance audit checks
// Large Data Volume detection
// Objects with 1M+ records need special handling
SELECT COUNT(Id) FROM Account // LDV if > 1M
SELECT COUNT(Id) FROM Contact // LDV if > 1M
SELECT COUNT(Id) FROM Task // LDV if > 5M
SELECT COUNT(Id) FROM Event // LDV if > 5M
// Wide object detection
// Objects with 400+ fields hit Salesforce's field limit
// Objects with 100+ custom fields are "wide" and
// cause performance degradation on page loads
// Storage analysis
// Data storage vs File storage split
// Approaching limit = Critical finding
// Cascade trigger analysis
// When Account update triggers Contact update which
// triggers Case update, the cascade depth is 3.
// Each level multiplies DML operations.
LDV objects get special attention because they affect query performance, data migration time, and Bulk API requirements. An Account table with 5 million records takes 45 minutes to export via Bulk API versus 30 seconds for 10,000 records. If you are planning a migration or an integration, this changes your timeline by weeks.
The Scoring Model
Every finding carries a severity. The scoring model converts severities into a single 0-100 health score:
fn calculate_audit_score(
critical: u32, warning: u32, info: u32
) -> u32 {
let penalty =
(critical * 15) + (warning * 5) + (info * 1);
100u32.saturating_sub(penalty).max(0)
}
// Examples:
// 0 critical, 0 warning, 3 info = 97/100 (Excellent)
// 1 critical, 2 warning, 5 info = 70/100 (Good)
// 3 critical, 5 warning, 10 info = 20/100 (Critical)
// 5 critical, 10 warning, 20 info = 0/100 (Critical)
The weights are intentional. A Critical finding costs 15 points because it represents a real risk: data loss, compliance violation, or production incident. A Warning costs 5 points because it should be addressed but is not immediately dangerous. An Info costs 1 point because it is a minor improvement opportunity.
The scoring model is aggressive. An org with 3 Critical findings and 5 Warnings scores 20/100. That is deliberate. An org with active security gaps and data quality problems is not healthy, and the score should reflect that. Padding the score to make it look better would defeat the purpose of the audit.
Cross-Dimension Correlation
The most valuable output of the audit engine is not any single finding. It is the correlations between findings across dimensions.
// Cross-dimension correlation engine
fn correlate_findings(
all_results: &[AuditResult]
) -> Vec<CrossDimensionFinding> {
let mut correlations = Vec::new();
// Index findings by object and field
let mut by_object: HashMap<String, Vec<&Finding>> =
HashMap::new();
for result in all_results {
for finding in &result.findings {
if let Some(ref obj) = finding.object {
by_object.entry(obj.clone())
.or_default()
.push(finding);
}
}
}
// Correlation 1: Security + Compliance
// PII field without field history tracking
for (object, findings) in &by_object {
let has_pii = findings.iter().any(|f|
f.category == "PII Detection");
let missing_history = findings.iter().any(|f|
f.category == "Field History"
&& f.severity != Severity::Info);
if has_pii && missing_history {
correlations.push(CrossDimensionFinding {
severity: Severity::Critical,
dimensions: vec!["Security", "Compliance"],
title: format!(
"{} has PII fields without audit trail",
object),
description: format!(
"Object {} contains PII fields that \
are not tracked by field history. This \
creates compliance and security risk.",
object),
});
}
}
// Correlation 2: Schema + Performance
// Wide object with LDV
for (object, findings) in &by_object {
let is_wide = findings.iter().any(|f|
f.category == "Wide Object");
let is_ldv = findings.iter().any(|f|
f.category == "Large Data Volume");
if is_wide && is_ldv {
correlations.push(CrossDimensionFinding {
severity: Severity::Critical,
dimensions: vec!["Schema", "Performance"],
title: format!(
"{} is both wide and high-volume",
object),
description: format!(
"Object {} has 100+ custom fields AND \
1M+ records. This combination causes \
severe performance degradation. \
Consider field archival or object \
splitting.", object),
});
}
}
// Correlation 3: Data Quality + Schema
// Required field with high null rate + no validation rule
for (object, findings) in &by_object {
let null_fields = findings.iter()
.filter(|f| f.category == "Required Field"
&& f.severity == Severity::Critical);
let has_validation = findings.iter().any(|f|
f.category == "Validation Rule");
for null_finding in null_fields {
if !has_validation {
correlations.push(CrossDimensionFinding {
severity: Severity::Critical,
dimensions: vec![
"Data Quality", "Schema"],
title: format!(
"{}: critical null values with no \
validation rule protection",
object),
description: format!(
"Field {} has {} null values and \
no validation rule prevents future \
nulls. The problem will grow.",
null_finding.field.as_deref()
.unwrap_or("unknown"),
null_finding.count.unwrap_or(0)),
});
}
}
}
correlations
}
These correlations are the findings that consultants charge $50,000 to discover manually. The audit engine finds them in seconds because it has all five dimensions in memory simultaneously. A human auditor checking data quality would not naturally think to check whether the affected fields are PII. The engine does because it is designed to.
The Recommendation Engine
Findings tell you what is wrong. Recommendations tell you what to do about it, sorted by effort and impact:
fn generate_recommendations(
findings: &[Finding]
) -> Vec<Recommendation> {
let mut recommendations = Vec::new();
let mut priority = 1;
// Critical required field issues
let critical_required: Vec<_> = findings.iter()
.filter(|f| f.severity == Severity::Critical
&& f.category == "Required Field")
.collect();
if !critical_required.is_empty() {
recommendations.push(Recommendation {
priority,
title: "Address critical required field gaps"
.to_string(),
description: format!(
"Found {} critical required field issues \
affecting thousands of records. These \
must be resolved before migration.",
critical_required.len()),
effort: "Medium".to_string(),
impact: "High".to_string(),
related_findings: critical_required.iter()
.map(|f| f.id.clone()).collect(),
});
priority += 1;
}
// Duplicate records
let dup_findings: Vec<_> = findings.iter()
.filter(|f| f.category == "Duplicate Detection")
.collect();
if !dup_findings.is_empty() {
recommendations.push(Recommendation {
priority,
title: "Deduplicate records before migration"
.to_string(),
description: "Duplicate records found. Merging \
before migration will reduce data volume \
and improve data quality in target system."
.to_string(),
effort: "Medium".to_string(),
impact: "Medium".to_string(),
related_findings: dup_findings.iter()
.map(|f| f.id.clone()).collect(),
});
priority += 1;
}
recommendations
}
Recommendations are sorted by a priority score derived from the ratio of impact to effort. High impact, low effort goes first. Low impact, high effort goes last. This gives stakeholders a clear action sequence: do these three things first, then these five things, then these two things if you have time.
Multi-Org Comparison
BridgeQL's compare module runs the same audit across multiple orgs and generates a side-by-side comparison. This is essential for multi-org environments where you need to understand how orgs have diverged.
// From bridgeql-assess/src/compare.rs
pub struct OrgComparator {
org_dirs: Vec<String>,
}
impl OrgComparator {
pub fn compare(&self) -> Result<String> {
let orgs = self.load_all()?;
// Collect all unique objects across orgs
let mut all_objects: HashSet<String> = HashSet::new();
for org in &orgs {
for count in &org.record_counts {
all_objects.insert(count.object.clone());
}
}
// Build per-org record count maps
let org_counts: Vec<HashMap<String, u64>> = orgs.iter()
.map(|org| {
org.record_counts.iter()
.map(|c| (c.object.clone(), c.count))
.collect()
})
.collect();
// Find custom objects not shared across all orgs
let custom_per_org: Vec<HashSet<String>> = orgs.iter()
.map(|org| {
org.record_counts.iter()
.filter(|c| c.object.contains("__c"))
.map(|c| c.object.clone())
.collect()
})
.collect();
let common_custom: HashSet<String> = custom_per_org
.iter().cloned()
.reduce(|a, b| a.intersection(&b).cloned().collect())
.unwrap_or_default();
// Generate comparison report
// ...
}
}
The comparison output shows:
Multi-Org Comparison Report
Orgs Compared: 3
| Metric | sales-prod | support-prod | marketing-prod |
|-----------------|------------|--------------|----------------|
| Custom Objects | 87 | 45 | 32 |
| Apex Classes | 234 | 67 | 12 |
| Apex Triggers | 45 | 23 | 3 |
| Active Flows | 89 | 34 | 56 |
| Total Records | 4,200,000 | 8,900,000 | 1,200,000 |
Custom objects NOT in all orgs:
Revenue_Forecast__c (only in sales-prod)
Escalation_Path__c (only in support-prod)
Campaign_Attribution__c (only in marketing-prod)
This comparison answers the question every multi-org architect dreads: "How different are our orgs, really?" The answer is usually "more different than anyone thought." And knowing the specifics is the first step toward consolidation, synchronization, or rational multi-org architecture.
Report Generation
The audit engine generates a full assessment report combining all dimensions:
pub struct AssessmentReport {
pub client_name: String,
pub org_id: String,
pub org_alias: String,
pub generated_at: String,
pub schema_audit: Option<AuditResult>,
pub quality_audit: Option<AuditResult>,
pub security_audit: Option<AuditResult>,
pub automation_audit: Option<AuditResult>,
pub performance_audit: Option<AuditResult>,
pub compliance_audit: Option<AuditResult>,
pub overall_score: u32,
pub executive_summary: String,
pub top_findings: Vec<Finding>,
pub top_recommendations: Vec<Recommendation>,
}
The report is structured for two audiences. The executive summary and overall score are for leadership: "Your org scores 62/100. Here are the top 3 actions that will improve it." The detailed findings per dimension are for the technical team: "Account.Industry has 3,247 null values. Here is the SOQL to find them. Here is the remediation."
The entire audit runs in under 2 minutes for a typical org (400 objects, 5M records). Compare that to a manual audit that takes 2-4 weeks. And the automated audit is repeatable. Run it monthly. Watch the score trend. Know whether your org is getting healthier or sicker.
That is the value proposition of automated org intelligence. Not a one-time report. A continuous measurement system that catches problems before they become crises and quantifies health in a way that stakeholders can track over time.