Request middleware

Global middleware sets JSON responses and the request locale. Protected package routes use the parent exowpee.teams and an access group. Controllers then validate input and call services, which authorize through policies and return resources.

Scope context parent

exowpee.teams is the alias for IAM’s TeamPermissions. It clears the previous Spatie team ID, stored request context and loaded permission relations. For an authenticated token it checks owner claims against iam_scopes, then sets the selected owner ID as Spatie’s team ID. A token cannot carry both a company and a client selection. The scope’s type and organization must match the authenticated guard and claims. The parent fills context; it does not authorize business actions.

Complete access groups

Each composes auth:<guards>, exowpee.set.guard, exowpee.context, exowpee.active.actor and exowpee.verified.user. The four guards retain separate provider/type checks. Active-actor middleware validates the session key, actor and selected owner/profile/membership. Verification applies to human users, not agents. A null or mismatched stored session hash is rejected with 401. Raw auth:api,agent and guard selection alone omit these protections. Existing manually composed routes must include equivalent checks; use a complete group when adding routes. The access context middleware checks allowed owner and actor types, not whether an owner has been selected. Shared human self-service and profile switching work with an unscoped login token. Scoped services must resolve currentScope() and authorize their resources.

Helpers after authentication

  • auth()->user() returns the guard’s User, ClientUser, Agent or ClientAgent model.
  • auth()->company() resolves company context; client guards do not supply a company.
  • auth()->client() resolves client context for client guards.
  • auth()->profile() resolves the selected, scope-consistent profile.
  • auth()->currentUserProfile() resolves the human assignment.
  • getTeamOrganization() resolves the selected scope’s organization.
Do not treat the existence of an access-group name as proof that a scope exists. Do not infer a company from getPermissionsTeamId() in shared IAM code. See IAM overview for actor/context mapping.

Validation layer

FormRequest

Each endpoint has a dedicated FormRequest class that:
  1. Validates input — Returns 422 Unprocessable Entity with field-specific errors
  2. Authorizes the action — Can return 403 Forbidden
Validation errors are formatted by ValidationErrorFormatter for consistent, human-readable messages.

Authorization layer

Policies

Every model has a Policy class that defines who can perform each action:
BeforePolicyFilterTrait denies an inactive actor, then permits the administrator role before checking working hours or the action method. IAM and Team services call a separate protection Gate before the permission Gate. IamProtectionPolicy and TeamProtectionPolicy do not use BeforePolicyFilterTrait. They enforce company/client and organization scope, builtin immutability, and ownership of the active default-profile preference. The permission policies use the shared trait directly, without overriding before(). For example, deleting a custom profile requires both checks:
The iam-protection.* and team-protection.* Gates are internal rules. They are not discovered or granted as permissions. Every affected service operation must call both Gates; calling the permission Gate alone does not enforce protection. Default changes use iam-protection.default; changing hours, activation or deletion of an assignment uses iam-protection.assignment. Builtin assignments allow only the user’s own active default preference. A future module entitlement check, such as access to accounting under a paid plan, can use another explicit Gate without the administrator shortcut. It would check the subscription of the selected company/client before the operation’s permission Gate. No subscription check is implemented in this change.

Business logic layer

Service classes

Controllers are kept thin — they delegate to Service classes for business logic:
Services handle:
  • Complex creation logic (e.g., product + variants + images in one operation)
  • Cross-model operations (e.g., stock updates when confirming an order)
  • Event dispatching
  • Integration with other packages via contracts

Response layer

API Resources

Every response is transformed through a Resource class that controls which fields are exposed:

Refresh broadcasts

Notification implements queued ClientRefreshEvent broadcasts for company and client scopes. Profile switching resets the actor’s read set and broadcasts a refresh signal. Automatic business invalidation is not wired into production callers yet. See broadcasting for the implemented channels, worker requirements and revocation limitation.