
Explain who changed customer permissions
Changing a person’s roles changes what they can do. Wildo records that change as an authority event, identifying the actor and the previous, requested and resulting roles where applicable.
Organization-wide memberships and assignments inside one unit remain distinct. The evidence can explain both broad customer administration and a narrower team-level grant.
Example — Understand how a member became a manager
A customer administrator changes a member’s role. The evidence keeps the before-and-after roles and the requested set, while a separate unit assignment shows that another permission applies only inside one team.
For engineers
Declare and invoke roles through the resource’s membership operations and their registered custom implementations. Their role-ceiling and administrative-continuity checks still apply. Direct database edits bypass this operation lifecycle and cannot be expected to produce its semantic evidence.
The following selected emission from organization-member-custom-implementation.backend.service.ts shows why authority evidence contains more than the final row:
await emitAuthorityChangeAuditWhenDurable({
serviceOptions: utils.serviceOptions,
effectKey: ['organization-member-roles-changed', memberId, pending.operation].join(':'),
logger: utils.logger,
failureContext: { operation: pending.operation, memberId, organizationId },
emit: async () => {
const auditLogsService = container.get<AuditLogsBackendService>(SAAS_SERVICE_TYPES.AuditLogsService);
await auditLogsService.logOrganizationMemberRolesChanged({
organizationId,
memberId,
userId,
previousRoles: pending.previousRoles,
newRoles: pending.newRoles,
requestedRoles: pending.requestedRoles,
operation: pending.operation,
changedBy: resolveAuditActorId(executionContext),
}, executionContext);
},
});
The operation stages its inputs before persistence, then emits through the durability helper. requestedRoles preserves what the caller authored; newRoles describes the resulting set. This matters when defaults, validation or grant ceilings distinguish the request from the stored authority.
Keep the scope visible in the event
| Change | Event family | Evidence to inspect |
|---|---|---|
| Ordinary organization membership role change | ORGANIZATION_MEMBER_ROLE_CHANGED | previousRoles, newRoles, requestedRoles, member and organization |
| Membership removal or invitation revocation | ORGANIZATION_MEMBER_REMOVED | Roles, previousStatus and revokedUnitAssignments, captured before deletion |
| Unit role assignment | ORGANIZATION_UNIT_MEMBER_ROLE_CHANGED | previousRole, newRole, requestedRole, assignment and unit |
| Direct unit assignment removal | ORGANIZATION_UNIT_MEMBER_REMOVED | The removed assignment and its revokedRole, captured before deletion |
Named ownership repair (GRANT_OWNERSHIP) | RESOURCE_OPERATION_PERFORMED | Operation identity, affected member, changedFields and role sets in fieldEvidence when changed |
| Application roles | Separate application-scope authority events | The affected principal’s application authority, not customer membership |
Include ownership repair in an authority review
The ownership-repair action uses update-like persistence, but its custom implementation is registered under its own operation identity. It does not inherit the ordinary UPDATE hook that emits ORGANIZATION_MEMBER_ROLE_CHANGED. Its critical risk instead triggers automatic RESOURCE_OPERATION_PERFORMED evidence; the roles field is marked for before-and-after evidence.
A query restricted to ORGANIZATION_MEMBER_ROLE_CHANGED can therefore miss ownership repair. Query the automatic RESOURCE_OPERATION_PERFORMED event family, then inspect the returned eventData.resourceType and eventData.operationIdentifier for the membership resource and GRANT_OWNERSHIP operation. Those nested fields are not LIST query filters. Inspect fieldEvidence for the role change; do not expect the specialized event’s requestedRoles field in this payload. An idempotent repair can leave the roles unchanged.
Correlate the operation evidence with the crossing observation by actor, operation, affected record and request context. Resolve the observation’s grant ID together with its grant scope to inspect the grant lifecycle. These are complementary records, not identical payloads or an atomic evidence bundle.
A unit assignment holds one role per assignment record. Treating it as an organization-wide role array would erase the scope that limits its authority. The audit subject identifies the acting principal; the affected user/member and assignment remain explicit event data.
Removing a membership also removes the record that explained its access. Capturing its status distinguishes a revoked invitation from an active membership removal; retaining the unit assignments explains the narrower authority removed with it.
Example: investigate a membership role change
After a role change, query the customer’s audit resource using an authenticated session authorized for that organization. This illustrative request uses the declared event-type filter; replace the organization placeholder and use the application’s API base URL:
GET /organizations/{organizationId}/audit-logs?eventType=organization_member_role_changed
The default LIST variant declares organization member, organization administrator and application super-administrator roles. The route and authorization context still constrain access: a role name or an organization ID in a URL does not grant access to another customer’s trail. See the audit inspection and export API for the full read/filter contract.
Read the returned data records as follows:
| Field | How to interpret the result |
|---|---|
userId on the audit record, and eventData.changedBy | The actor who made the change; the userId query filter selects this actor |
eventData.userId and eventData.memberId | The affected user and membership, not necessarily the actor |
eventData.previousRoles | Authority before the mutation |
eventData.requestedRoles | The submitted role set, when supplied |
eventData.newRoles | Authority resulting from the operation |
For example, if an administrator changes another member’s roles, the actor filter finds the administrator’s action while the event data identifies the member affected. Compare requested and resulting roles rather than assuming they are identical. A repeated unchanged role set emits no new role-change event; an empty result alone also cannot establish that audit delivery was healthy.
Record durable changes, not rolled-back intentions
The helper emits after the relevant write is durable and defers to an outer transaction’s post-commit lane when necessary. A rollback must not leave an event claiming a grant that never took effect. Repeating the same role set produces no role-change event.
Cascade removal follows the parent lifecycle’s evidence rather than fabricating an individual revocation for every child assignment. Audit routing also uses authenticated context; a system provisioning action can reach the shared audit store without an authenticated customer routing context.
These events explain changes made through the framework lifecycle. Preserve the supplied registrations and transaction context when extending that lifecycle, and monitor audit-delivery diagnostics rather than treating the event stream as an infallible transactional replica.