Skip to main content
Wildo.ai Coming soon

Shared services and application authority

Explain who changed customer permissions

Organization-wide and unit-scoped role changes produce distinct evidence of the requested and resulting authority, attributed to the actor who changed it.

An authority-change record explains who changed a role and whose authority changed.

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

ChangeEvent familyEvidence to inspect
Ordinary organization membership role changeORGANIZATION_MEMBER_ROLE_CHANGEDpreviousRoles, newRoles, requestedRoles, member and organization
Membership removal or invitation revocationORGANIZATION_MEMBER_REMOVEDRoles, previousStatus and revokedUnitAssignments, captured before deletion
Unit role assignmentORGANIZATION_UNIT_MEMBER_ROLE_CHANGEDpreviousRole, newRole, requestedRole, assignment and unit
Direct unit assignment removalORGANIZATION_UNIT_MEMBER_REMOVEDThe removed assignment and its revokedRole, captured before deletion
Named ownership repair (GRANT_OWNERSHIP)RESOURCE_OPERATION_PERFORMEDOperation identity, affected member, changedFields and role sets in fieldEvidence when changed
Application rolesSeparate application-scope authority eventsThe 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:

FieldHow to interpret the result
userId on the audit record, and eventData.changedByThe actor who made the change; the userId query filter selects this actor
eventData.userId and eventData.memberIdThe affected user and membership, not necessarily the actor
eventData.previousRolesAuthority before the mutation
eventData.requestedRolesThe submitted role set, when supplied
eventData.newRolesAuthority 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.

Building a B2B product or an internal tool?

Wildo is not self-service yet. Tell us what you have in mind and we will say plainly whether it fits, and what happens next.