artisan('lottery:admin-auth-sync')->assertExitCode(0); }); function grantDelegationAgentOperator(AdminUser $admin, AgentNode $agent, bool $manage = true): void { $now = now(); $roleId = DB::table('admin_roles')->insertGetId([ 'slug' => 'agent_ops_'.$admin->id, 'code' => 'agent_ops_'.$admin->id, 'name' => 'Agent Ops', 'description' => null, 'status' => 1, 'is_system' => false, 'sort_order' => 0, 'created_at' => $now, 'updated_at' => $now, ]); $codes = $manage ? [ 'agent.node.view', 'agent.node.manage', 'agent.role.view', 'agent.role.manage', 'agent.user.view', 'agent.user.manage', ] : ['agent.node.view']; $actionIds = DB::table('admin_menu_actions') ->whereIn('permission_code', $codes) ->pluck('id'); foreach ($actionIds as $actionId) { DB::table('admin_role_menu_actions')->insert([ 'role_id' => $roleId, 'menu_action_id' => (int) $actionId, ]); } DB::table('admin_user_site_roles')->insert([ 'admin_user_id' => $admin->id, 'site_id' => (int) $agent->admin_site_id, 'role_id' => $roleId, 'granted_at' => $now, ]); DB::table('admin_user_agents')->insert([ 'admin_user_id' => $admin->id, 'agent_node_id' => (int) $agent->id, 'is_primary' => true, 'granted_at' => $now, ]); DB::table('admin_user_agent_roles')->insert([ 'admin_user_id' => $admin->id, 'agent_node_id' => (int) $agent->id, 'role_id' => $roleId, 'granted_at' => $now, ]); } test('parent agent can sync delegation grants for direct child', function (): void { $siteId = (int) DB::table('admin_sites')->where('is_default', true)->value('id'); $rootId = (int) DB::table('agent_nodes')->where('admin_site_id', $siteId)->where('depth', 0)->value('id'); $service = app(\App\Services\Agent\AgentNodeService::class); $super = AdminUser::query()->create([ 'username' => 'deleg_super', 'name' => 'Super', 'email' => null, 'password' => Hash::make('secret-strong'), 'status' => 0, ]); grantSuperAdminRole($super); $parent = $service->createChild($super, [ 'parent_id' => $rootId, 'code' => 'deleg-parent', 'name' => 'Deleg Parent', ]); $child = $service->createChild($super, [ 'parent_id' => $parent->id, 'code' => 'deleg-child', 'name' => 'Deleg Child', ]); $parentAdmin = AdminUser::query()->create([ 'username' => 'deleg_parent_admin', 'name' => 'Parent Admin', 'email' => null, 'password' => Hash::make('secret-strong'), 'status' => 0, ]); grantDelegationAgentOperator($parentAdmin, $parent, manage: true); $viewActionId = (int) DB::table('admin_menu_actions') ->where('permission_code', 'agent.node.view') ->value('id'); $token = $parentAdmin->createToken('test', ['*'], now()->addDay())->plainTextToken; $this->withHeader('Authorization', 'Bearer '.$token) ->putJson('/api/v1/admin/agent-nodes/'.$child->id.'/delegation-grants', [ 'grants' => [ ['menu_action_id' => $viewActionId, 'can_delegate' => true], ], ]) ->assertOk() ->assertJsonPath('data.child_agent_id', $child->id) ->assertJsonCount(1, 'data.grants'); $this->withHeader('Authorization', 'Bearer '.$token) ->getJson('/api/v1/admin/agent-nodes/'.$child->id.'/delegation-grants') ->assertOk() ->assertJsonPath('data.grants.0.can_delegate', true); }); test('delegation ceiling blocks role permissions beyond child grants', function (): void { $siteId = (int) DB::table('admin_sites')->where('is_default', true)->value('id'); $rootId = (int) DB::table('agent_nodes')->where('admin_site_id', $siteId)->where('depth', 0)->value('id'); $service = app(\App\Services\Agent\AgentNodeService::class); $super = AdminUser::query()->create([ 'username' => 'ceil_super', 'name' => 'Super', 'email' => null, 'password' => Hash::make('secret-strong'), 'status' => 0, ]); grantSuperAdminRole($super); $branch = $service->createChild($super, [ 'parent_id' => $rootId, 'code' => 'ceil-branch', 'name' => 'Ceil Branch', ]); $viewActionId = (int) DB::table('admin_menu_actions') ->where('permission_code', 'agent.node.view') ->value('id'); DB::table('agent_delegation_grants')->insert([ 'parent_agent_id' => $rootId, 'child_agent_id' => $branch->id, 'menu_action_id' => $viewActionId, 'can_delegate' => true, 'granted_at' => now(), 'created_at' => now(), 'updated_at' => now(), ]); $operator = AdminUser::query()->create([ 'username' => 'ceil_ops', 'name' => 'Ops', 'email' => null, 'password' => Hash::make('secret-strong'), 'status' => 0, ]); grantDelegationAgentOperator($operator, $branch, manage: true); $token = $operator->createToken('test', ['*'], now()->addDay())->plainTextToken; $this->withHeader('Authorization', 'Bearer '.$token) ->postJson('/api/v1/admin/agent-nodes/'.$branch->id.'/roles', [ 'slug' => 'ceil_ok', 'name' => 'OK', 'permission_slugs' => ['prd.agent.view'], ]) ->assertOk(); $this->withHeader('Authorization', 'Bearer '.$token) ->postJson('/api/v1/admin/agent-nodes/'.$branch->id.'/roles', [ 'slug' => 'ceil_bad', 'name' => 'Bad', 'permission_slugs' => ['prd.dashboard.view'], ]) ->assertStatus(422); }); test('auth me includes delegation ceiling for agent user', function (): void { $siteId = (int) DB::table('admin_sites')->where('is_default', true)->value('id'); $root = AgentNode::query()->where('admin_site_id', $siteId)->where('depth', 0)->firstOrFail(); $admin = AdminUser::query()->create([ 'username' => 'me_deleg', 'name' => 'Me', 'email' => null, 'password' => Hash::make('secret-strong'), 'status' => 0, ]); grantDelegationAgentOperator($admin, $root, manage: false); $token = $admin->createToken('test', ['*'], now()->addDay())->plainTextToken; $this->withHeader('Authorization', 'Bearer '.$token) ->getJson('/api/v1/admin/auth/me') ->assertOk() ->assertJsonStructure(['data' => ['admin' => ['delegation_ceiling', 'operational_permissions']]]); });