Update submodules: botserver, botui and project guidelines.

This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2026-04-05 09:12:32 -03:00
parent 93dc55c47c
commit fc95cba887
4 changed files with 610 additions and 1302 deletions

429
.opencode/plans/folders.md Normal file
View file

@ -0,0 +1,429 @@
# Folders.md: Sistema de Permissões de Pastas (Estilo Windows ACL)
## Visão Geral
Implementar controle de acesso a pastas baseado em grupos RBAC, permitindo que `USE KB` inclua seletivamente pastas conforme os grupos do usuário.
## Arquitetura Atual
### Já Existe ✅
| Componente | Arquivo | Estado |
|------------|---------|--------|
| `KbPermissions` | `core/kb/permissions.rs` | Completo - `AccessLevel::GroupBased`, `FolderPermission` |
| `UserContext` | `core/kb/permissions.rs` | Tem `groups: Vec<String>` |
| `build_qdrant_permission_filter()` | `core/kb/permissions.rs` | Gera filtros Qdrant por grupo |
| `rbac_groups` | Schema core.rs | Tabela existe |
| `rbac_user_groups` | Schema core.rs | Tabela existe (user → group) |
| `file_shares` | migrations drive | Tem `shared_with_group` |
### Falta Integrar ❌
| Componente | Descrição |
|------------|-----------|
| `folder_group_access` | Tabela para link pasta → grupo |
| `UserContext.groups` | Popular grupos do BD na sessão |
| `USE KB` permission check | Verificar grupos antes de adicionar |
| UI Admin | Atribuir grupos a pastas |
| `USE FOLDER` keyword | BASIC keyword para pastas |
---
## Estrutura de Permissões (Windows-style)
```
Organização
├── Gestores (grupo RBAC)
│ ├── Pasta: /relatorios/financeiros
│ │ └── Permissão: Gestores (ler/escrever)
│ ├── Pasta: /strategic
│ │ └── Permissão: Gestores (ler)
│ └── Pasta: /publico
│ └── Permissão: Todos (ler)
├── RH (grupo RBAC)
│ ├── Pasta: /rh/documentos
│ │ └── Permissão: RH (ler/escrever)
│ └── Pasta: /relatorios/financeiros
│ └── Permissão: RH (ler)
└── Todos (grupo implícito)
├── Pasta: /publico
│ └── Permissão: Todos (ler)
└── Pasta: /intranet
└── Permissão: Autenticados (ler)
```
---
## Plano de Implementação
### Fase 1: Database (Migration)
**Arquivo:** `botserver/migrations/6.2.0-02-folder-access/up.sql`
```sql
-- Tabela principal: pasta ↔ grupo
CREATE TABLE folder_group_access (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
folder_path TEXT NOT NULL, -- Ex: "work/bot1/pasta-protegida"
group_id UUID NOT NULL REFERENCES rbac_groups(id) ON DELETE CASCADE,
permission_level TEXT NOT NULL DEFAULT 'read', -- read|write|admin
created_by UUID REFERENCES users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(folder_path, group_id)
);
-- Índice para busca rápida
CREATE INDEX idx_folder_group_access_path ON folder_group_access(folder_path);
CREATE INDEX idx_folder_group_access_group ON folder_group_access(group_id);
-- Adicionar coluna de permissões em kb_collections
ALTER TABLE kb_collections
ADD COLUMN IF NOT EXISTS access_level TEXT DEFAULT 'authenticated';
COMMENT ON TABLE folder_group_access IS 'Windows-style ACL: pasta ↔ grupo RBAC';
```
---
### Fase 2: Schema Diesel
**Arquivo:** `botserver/src/core/shared/schema/research.rs`
```rust
diesel::table! {
folder_group_access (id) {
id -> Uuid,
folder_path -> Text,
group_id -> Uuid,
permission_level -> Varchar,
created_by -> Nullable<Uuid>,
created_at -> Timestamptz,
}
}
diesel::joinable!(folder_group_access -> rbac_groups (group_id));
// Adicionar em kb_collections:
access_level -> Varchar, // all|authenticated|role_based|group_based
```
---
### Fase 3: Modelos Rust
**Arquivo:** `botserver/src/core/kb/models.rs` (novo)
```rust
#[derive(Debug, Clone, Queryable, Selectable)]
#[diesel(table_name = folder_group_access)]
pub struct FolderGroupAccess {
pub id: Uuid,
pub folder_path: String,
pub group_id: Uuid,
pub permission_level: String,
pub created_by: Option<Uuid>,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Insertable)]
#[diesel(table_name = folder_group_access)]
pub struct NewFolderGroupAccess {
pub folder_path: String,
pub group_id: Uuid,
pub permission_level: String,
pub created_by: Option<Uuid>,
}
```
---
### Fase 4: Carregar Grupos do Usuário
**Arquivo:** `botserver/src/core/shared/state.rs`
Modificar `AppState` ou `UserContext` para popular grupos:
```rust
// Nova função em core/kb/permissions.rs
pub async fn load_user_groups(
db_pool: &DbPool,
user_id: Uuid,
) -> Result<Vec<String>, String> {
use crate::core::shared::schema::core::rbac_groups::dsl::*;
use crate::core::shared::schema::core::rbac_user_groups::dsl::*;
let mut conn = db_pool.get().map_err(|e| e.to_string())?;
let group_names: Vec<String> = rbac_user_groups
.inner_join(rbac_groups)
.filter(user_id.eq(user_id))
.select(name)
.load(&mut conn)
.map_err(|e| e.to_string())?;
Ok(group_names)
}
// Em UserContext, adicionar método:
impl UserContext {
pub async fn with_db_groups(mut self, db_pool: &DbPool) -> Result<Self, String> {
let groups = load_user_groups(db_pool, self.user_id).await?;
self.groups = groups;
Ok(self)
}
}
```
---
### Fase 5: Modificar USE KB
**Arquivo:** `botserver/src/basic/keywords/use_kb.rs`
```rust
use crate::core::kb::permissions::{KbPermissionParser, FolderPermission, AccessLevel};
fn add_kb_to_session(
conn_pool: DbPool,
session_id: Uuid,
bot_id: Uuid,
user_id: Uuid, // Adicionar
kb_name: &str,
) -> Result<(), String> {
// ... código existente ...
// NOVO: Verificar permissões de grupo
let user_groups = load_user_groups(&conn_pool, user_id)?;
let has_access = check_folder_group_access(
&conn_pool,
&kb_folder_path,
&user_groups,
)?;
if !has_access {
return Err(format!(
"Acesso negado: KB '{}' requer grupo específico",
kb_name
));
}
// ... resto do código ...
}
fn check_folder_group_access(
conn_pool: &DbPool,
folder_path: &str,
user_groups: &[String],
) -> Result<bool, String> {
// Buscar grupos associados à pasta
// Se pasta é "pública" (sem grupos) → permitir
// Se usuário está em algum grupo da pasta → permitir
// Caso contrário → negar
}
```
---
### Fase 6: Modificar THINK KB (Filtro Qdrant)
**Arquivo:** `botserver/src/basic/keywords/think_kb.rs`
```rust
use crate::core::kb::permissions::build_qdrant_permission_filter;
async fn think_kb_search(
// ... parâmetros ...
user_id: Uuid,
) -> Result<Value, String> {
// Carregar contexto do usuário com grupos
let user_groups = load_user_groups(&db_pool, user_id).await?;
let user_context = UserContext::authenticated(
user_id,
Some(email),
org_id,
).with_groups(user_groups);
// Filtrar resultados do Qdrant com base nos grupos
let qdrant_filter = build_qdrant_permission_filter(&user_context);
// Buscar no Qdrant com filtro
// ...
}
```
---
### Fase 7: Novo Keyword USE FOLDER
**Arquivo:** `botserver/src/basic/keywords/use_folder.rs` (novo)
```rust
// USE FOLDER "caminho/da/pasta" [READ|WRITE|ADMIN]
engine.register_custom_syntax(
["USE", "FOLDER", "$expr$", "($expr$)", "($expr$)", "($expr$)"],
true,
move |context, inputs| {
let folder_path = context.eval_expression_tree(&inputs[0])?.to_string();
// Verificar acesso, adicionar à sessão
},
);
```
---
### Fase 8: API Endpoints
**Arquivo:** `botserver/src/api/routes/rbac.rs`
```rust
// GET /api/rbac/folders/{path}/groups
// Lista grupos com acesso a uma pasta
async fn get_folder_groups(
Path(folder_path): Path<String>,
State(state): State<ApiState>,
) -> Result<Json<Vec<GroupInfo>>, AppError> {
// Query folder_group_access
}
// POST /api/rbac/folders/{path}/groups/{group_id}
async fn add_folder_group(
Path((folder_path, group_id)): Path<(String, Uuid)>,
Json(payload): Json<FolderAccessPayload>,
) -> Result<Json<FolderGroupAccess>, AppError> {
// INSERT folder_group_access
}
// DELETE /api/rbac/folders/{path}/groups/{group_id}
async fn remove_folder_group(
Path((folder_path, group_id)): Path<(String, Uuid)>,
) -> Result<StatusCode, AppError> {
// DELETE folder_group_access
}
// GET /api/rbac/users/{user_id}/accessible-folders
async fn get_user_accessible_folders(
// Lista pastas que o usuário pode acessar
)
```
---
### Fase 9: UI Admin
**Arquivo:** `botui/ui/suite/admin/groups.html`
Adicionar aba "Pastas" na visualização do grupo:
```html
<!-- Tab de Pastas -->
<div hx-get="/api/admin/groups/{group_id}/folders"
hx-target="#group-folders">
<button class="tab-btn">Pastas</button>
</div>
<div id="group-folders" class="tab-content">
<!-- Lista de pastas com acesso -->
<!-- Botão: Adicionar pasta -->
</div>
```
**Arquivo:** `botui/ui/suite/drive/drive.html`
Mostrar cadeado nas pastas protegidas:
```html
<i class="fa-solid fa-lock" title="Acesso restrito a grupos"></i>
```
---
## Fluxo Completo
```
1. Usuário executa: USE KB "relatorios-financeiros"
2. Sistema carrega:
- user_id da sessão
- grupos do usuário (rbac_user_groups → rbac_groups)
- grupos da pasta (folder_group_access)
3. Verificação:
- Se pasta não tem restrições (pública) → OK
- Se usuário está em algum grupo da pasta → OK
- Caso contrário → ERRO "Acesso negado"
4. Se OK:
- Adiciona KB em session_kb_associations
- THINK KB agora busca no Qdrant com filtro de grupos
5. THINK KB retorna:
- Apenas documentos de pastas que o usuário tem acesso
```
---
## Testes
```rust
#[test]
fn test_group_access_allowed() {
let groups = vec!["gestores".to_string()];
let folder_path = "work/bot/financeiro";
// Gestor tem acesso
assert!(check_folder_group_access(folder_path, &groups).unwrap());
}
#[test]
fn test_group_access_denied() {
let groups = vec!["rh".to_string()];
let folder_path = "work/bot/financeiro";
// RH não tem acesso a financeiro
assert!(!check_folder_group_access(folder_path, &groups).unwrap());
}
#[test]
fn test_public_folder_access() {
let groups = vec![];
let folder_path = "work/bot/publico";
// Pasta pública permite todos
assert!(check_folder_group_access(folder_path, &groups).unwrap());
}
```
---
## Prioridades de Implementação
| # | Tarefa | Prioridade | Complexidade |
|---|--------|------------|--------------|
| 1 | Migration folder_group_access | Alta | Baixa |
| 2 | Schema Diesel | Alta | Baixa |
| 3 | load_user_groups() | Alta | Média |
| 4 | check_folder_group_access() | Alta | Média |
| 5 | Modificar USE KB | Alta | Média |
| 6 | Modificar THINK KB (Qdrant filter) | Alta | Média |
| 7 | API endpoints | Média | Média |
| 8 | UI Admin | Média | Alta |
| 9 | USE FOLDER keyword | Baixa | Média |
---
## Arquivos a Modificar
| Arquivo | Ação |
|---------|------|
| `migrations/6.2.0-02-folder-access/up.sql` | Criar |
| `migrations/6.2.0-02-folder-access/down.sql` | Criar |
| `src/core/shared/schema/research.rs` | Modificar |
| `src/core/kb/permissions.rs` | Modificar (load_user_groups) |
| `src/core/kb/models.rs` | Criar |
| `src/basic/keywords/use_kb.rs` | Modificar |
| `src/basic/keywords/think_kb.rs` | Modificar |
| `src/api/routes/rbac.rs` | Modificar |
| `botui/ui/suite/admin/groups.html` | Modificar |
| `botui/ui/suite/drive/drive.html` | Modificar |

1479
AGENTS.md

File diff suppressed because it is too large Load diff

@ -1 +1 @@
Subproject commit 552f37a41c63cb5a86e147a5de3cc2555a521d3f
Subproject commit 155d465b14de5d018c7c3eeaf47abe5b3b95fb0c

2
botui

@ -1 +1 @@
Subproject commit 3919a857b2441c472dcdd1bd90b84734f6f10b02
Subproject commit 45f56f0f6e4c54c168d73464736fb3fc80f79026