Defensible edge · Auditor-grade by architecture

Integrity is a property of the data layer.

Most GRC platforms enforce auditor-grade behavior through RBAC configuration that admins can change. We enforce it architecturally — at the database, in the schema, in the code path. An admin cannot accidentally weaken the guarantees, and an examiner can verify them in the codebase.

Why architectural matters

RBAC config vs. data-layer enforcement.

Both can read identical to a buyer in a demo. They look very different to an examiner asking "prove it."

PropertyRBAC config (most platforms)Data-layer enforcement (us)
Auditor read-onlySet in admin panel · admin can change laterEnforced in database query layer · admin cannot bypass
Policy version immutabilityApp-level convention · could be edited via APISeparate locked record · created only at publish
Tenant isolationMiddleware filter · removable by config changePer-query filter · enforced at the read site
Audit trailLog table · admin can prune entriesAppend-only · soft-delete preserves history
Verified byTrust the vendor's claimsRead the source code · grep the filter calls
Four architectural pillars

Each one verifiable in the codebase.

01

AUDITOR role read-only at the data layer

The AUDITOR role is enforced in the database, not via UI permissions. An auditor can view, comment, and submit reviews — but cannot edit a record, accidentally or otherwise. An admin cannot grant write access to an auditor without changing the schema. The guarantee is structural.

Where it lives:
// app/lib/rbac.js — server-side enforcement on every API mutation
if (role === 'AUDITOR') {
  return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
02

Immutable PolicyVersion on publish

When a policy publishes, a new PolicyVersion record is created and locked. The Policy document continues to evolve through future drafts; the published version is permanently fixed. Auditors can see exactly what was published, when, and by whom — not what's currently in the editor.

Where it lives:
// Publish handler creates the locked version. Submit/approve never do.
await PolicyVersion.create({
  policyId, content, publishedAt: new Date(), publishedBy: userId,
});
03

Tenant isolation by query enforcement

Every read query for shared content uses getGrcOrgFilter, which resolves to { organizationId: { $in: [userOrgId, SYSTEM_ORG_ID] } } at the database layer. Tenant boundaries are enforced in the query, not in middleware that can be bypassed. Verified in the codebase pre-push grep.

Where it lives:
// Every shared-content read goes through this filter
const orgFilter = await getGrcOrgFilter(organizationId);
await Framework.find(orgFilter);
04

Soft-delete + AuditLog on every mutation

Audit-significant records (risks, policies, evidence) cannot be hard-deleted. Soft-delete sets isDeleted: true and writes an AuditLog row with actor, timestamp, and reason. Every mutation across the platform writes to AuditLog — including every Effy AI tool call, logged centrally as EFFY_TOOL_<name>.

Where it lives:
// Soft-delete pattern — never .deleteOne() on audit-significant records
await Risk.findOneAndUpdate(
  { _id: id, organizationId },
  { isDeleted: true, deletedAt, deletedBy: userId },
);
What this means for you

The difference shows up in three specific moments.

When an admin makes a configuration mistake

RBAC-config platforms: the auditor accidentally gets write access; nobody notices until something gets edited. Architectural: the database refuses the write; the mistake produces a 403 instead of a corrupted record.

When your examiner asks 'prove it'

RBAC-config platforms: you screenshot the admin panel and hope the examiner trusts the screenshot. Architectural: you walk them through the code path. Two minutes vs two days.

When you grow past 100 employees

RBAC-config platforms: the configuration drift problem grows linearly with team size — more admins means more chances to break the guarantees. Architectural: scale doesn't degrade integrity because integrity isn't in the configuration.

Read the code with us.

30-minute architecture walkthrough with our team. We'll show you the actual filter calls, the schema-level enforcement, and where AuditLog rows get written.