288 lines
9.3 KiB
TypeScript
288 lines
9.3 KiB
TypeScript
import { db } from "../../db/index";
|
|
import {
|
|
xosContractRegistry,
|
|
xosToolRegistry,
|
|
xosSkillRegistry,
|
|
xosPolicyRules,
|
|
xosAuditTrail,
|
|
type XosContract,
|
|
type InsertXosContract,
|
|
type XosTool,
|
|
type InsertXosTool,
|
|
type XosSkill,
|
|
type InsertXosSkill,
|
|
type XosPolicyRule,
|
|
type InsertXosPolicyRule,
|
|
type XosAuditEntry,
|
|
type InsertXosAuditEntry,
|
|
} from "@shared/schema";
|
|
import { eq, desc, and, sql } from "drizzle-orm";
|
|
|
|
export interface PolicyEvaluation {
|
|
allowed: boolean;
|
|
reason: string;
|
|
matchedPolicyId?: number;
|
|
matchedPolicyName?: string;
|
|
}
|
|
|
|
export interface AuditEvent {
|
|
agentName: string;
|
|
action: string;
|
|
target?: string;
|
|
decision: string;
|
|
justification?: string;
|
|
input?: any;
|
|
output?: any;
|
|
taskId?: number;
|
|
policyId?: number;
|
|
metadata?: any;
|
|
}
|
|
|
|
class GovernanceService {
|
|
async evaluatePolicy(
|
|
agent: string,
|
|
action: string,
|
|
target: string,
|
|
context?: any
|
|
): Promise<PolicyEvaluation> {
|
|
try {
|
|
const rules = await db
|
|
.select()
|
|
.from(xosPolicyRules)
|
|
.where(eq(xosPolicyRules.isActive, true))
|
|
.orderBy(xosPolicyRules.priority);
|
|
|
|
for (const rule of rules) {
|
|
if (!this.ruleMatchesRequest(rule, agent, action, target, context)) continue;
|
|
|
|
const allowed = rule.effect === "allow";
|
|
const evaluation: PolicyEvaluation = {
|
|
allowed,
|
|
reason: rule.description || `Regra "${rule.name}" ${allowed ? "permite" : "bloqueia"} esta ação`,
|
|
matchedPolicyId: rule.id,
|
|
matchedPolicyName: rule.name,
|
|
};
|
|
|
|
await this.recordAudit({
|
|
agentName: agent,
|
|
action,
|
|
target,
|
|
decision: allowed ? "allowed" : "denied",
|
|
justification: evaluation.reason,
|
|
input: context,
|
|
policyId: rule.id,
|
|
});
|
|
|
|
return evaluation;
|
|
}
|
|
|
|
await this.recordAudit({
|
|
agentName: agent,
|
|
action,
|
|
target,
|
|
decision: "allowed",
|
|
justification: "Nenhuma política aplicável - permitido por padrão",
|
|
input: context,
|
|
});
|
|
|
|
return {
|
|
allowed: true,
|
|
reason: "Nenhuma política aplicável - permitido por padrão",
|
|
};
|
|
} catch (error) {
|
|
console.error("[Governance] Erro ao avaliar política:", error);
|
|
return { allowed: false, reason: "Erro na avaliação de política - bloqueado por segurança (fail-closed)" };
|
|
}
|
|
}
|
|
|
|
private ruleMatchesRequest(
|
|
rule: XosPolicyRule,
|
|
agent: string,
|
|
action: string,
|
|
target: string,
|
|
context?: any
|
|
): boolean {
|
|
if (rule.scope === "tool") {
|
|
if (rule.target !== action && !action.startsWith(rule.target + ".")) return false;
|
|
}
|
|
if (rule.scope === "contract" && rule.target !== action) return false;
|
|
if (rule.scope === "agent" && rule.target !== agent) return false;
|
|
|
|
const conditions = rule.conditions as any;
|
|
if (!conditions || Object.keys(conditions).length === 0) return true;
|
|
|
|
if (conditions.pathPatterns && target) {
|
|
const blocked = conditions.pathPatterns.some((pattern: string) =>
|
|
target.includes(pattern) || target === pattern
|
|
);
|
|
if (blocked) return true;
|
|
return false;
|
|
}
|
|
|
|
if (conditions.blockedCommands && context?.command) {
|
|
const blocked = conditions.blockedCommands.some((cmd: string) =>
|
|
context.command.toLowerCase().includes(cmd.toLowerCase())
|
|
);
|
|
if (blocked) return true;
|
|
return false;
|
|
}
|
|
|
|
if (conditions.allowedAgents) {
|
|
return conditions.allowedAgents.includes(agent);
|
|
}
|
|
|
|
if (conditions.requiresHumanApproval) return true;
|
|
|
|
if (conditions.minValidationScore !== undefined && context?.validationScore !== undefined) {
|
|
return context.validationScore >= conditions.minValidationScore;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
async recordAudit(event: AuditEvent): Promise<void> {
|
|
try {
|
|
await db.insert(xosAuditTrail).values({
|
|
agentName: event.agentName,
|
|
action: event.action,
|
|
target: event.target || null,
|
|
decision: event.decision,
|
|
justification: event.justification || null,
|
|
input: event.input || null,
|
|
output: event.output || null,
|
|
taskId: event.taskId || null,
|
|
policyId: event.policyId || null,
|
|
metadata: event.metadata || null,
|
|
});
|
|
} catch (error) {
|
|
console.error("[Governance] Erro ao registrar audit:", error);
|
|
}
|
|
}
|
|
|
|
async syncToolsFromManager(tools: Array<{ name: string; description: string; category?: string }>): Promise<number> {
|
|
let synced = 0;
|
|
for (const tool of tools) {
|
|
try {
|
|
await db
|
|
.insert(xosToolRegistry)
|
|
.values({
|
|
name: tool.name,
|
|
category: tool.category || "general",
|
|
description: tool.description,
|
|
version: "1.0.0",
|
|
isActive: true,
|
|
})
|
|
.onConflictDoUpdate({
|
|
target: xosToolRegistry.name,
|
|
set: {
|
|
description: tool.description,
|
|
category: tool.category || "general",
|
|
},
|
|
});
|
|
synced++;
|
|
} catch (error) {
|
|
console.error(`[Governance] Erro ao sincronizar tool ${tool.name}:`, error);
|
|
}
|
|
}
|
|
console.log(`[Governance] ${synced} ferramentas sincronizadas no Tool Registry`);
|
|
return synced;
|
|
}
|
|
|
|
async registerContract(contract: InsertXosContract): Promise<XosContract> {
|
|
const [created] = await db.insert(xosContractRegistry).values(contract).returning();
|
|
return created;
|
|
}
|
|
|
|
async getContracts(): Promise<XosContract[]> {
|
|
return db.select().from(xosContractRegistry).where(eq(xosContractRegistry.isActive, true));
|
|
}
|
|
|
|
async getTools(): Promise<XosTool[]> {
|
|
return db.select().from(xosToolRegistry).where(eq(xosToolRegistry.isActive, true));
|
|
}
|
|
|
|
async createSkill(skill: InsertXosSkill): Promise<XosSkill> {
|
|
const [created] = await db.insert(xosSkillRegistry).values(skill).returning();
|
|
return created;
|
|
}
|
|
|
|
async getSkills(status?: string): Promise<XosSkill[]> {
|
|
if (status) {
|
|
return db.select().from(xosSkillRegistry).where(eq(xosSkillRegistry.status, status));
|
|
}
|
|
return db.select().from(xosSkillRegistry);
|
|
}
|
|
|
|
async getSkill(id: number): Promise<XosSkill | undefined> {
|
|
const [skill] = await db.select().from(xosSkillRegistry).where(eq(xosSkillRegistry.id, id));
|
|
return skill;
|
|
}
|
|
|
|
async incrementSkillUsage(id: number, success: boolean): Promise<void> {
|
|
const skill = await this.getSkill(id);
|
|
if (!skill) return;
|
|
const newCount = (skill.usageCount || 0) + 1;
|
|
const currentSuccesses = Math.round(((skill.successRate || 0) / 100) * (skill.usageCount || 0));
|
|
const newSuccesses = success ? currentSuccesses + 1 : currentSuccesses;
|
|
const newRate = Math.round((newSuccesses / newCount) * 100);
|
|
await db.update(xosSkillRegistry).set({ usageCount: newCount, successRate: newRate }).where(eq(xosSkillRegistry.id, id));
|
|
}
|
|
|
|
async deactivateSkill(id: number): Promise<void> {
|
|
await db.update(xosSkillRegistry).set({ status: "inactive" }).where(eq(xosSkillRegistry.id, id));
|
|
}
|
|
|
|
async updateToolRBAC(toolId: number, allowedAgents: string[]): Promise<void> {
|
|
await db.update(xosToolRegistry).set({
|
|
allowedAgents: allowedAgents.length > 0 ? allowedAgents : null,
|
|
}).where(eq(xosToolRegistry.id, toolId));
|
|
}
|
|
|
|
async getPolicies(): Promise<XosPolicyRule[]> {
|
|
return db.select().from(xosPolicyRules).where(eq(xosPolicyRules.isActive, true)).orderBy(xosPolicyRules.priority);
|
|
}
|
|
|
|
async createPolicy(policy: InsertXosPolicyRule): Promise<XosPolicyRule> {
|
|
const [created] = await db.insert(xosPolicyRules).values(policy).returning();
|
|
return created;
|
|
}
|
|
|
|
async getAuditTrail(limit: number = 50, agentName?: string): Promise<XosAuditEntry[]> {
|
|
if (agentName) {
|
|
return db.select().from(xosAuditTrail)
|
|
.where(eq(xosAuditTrail.agentName, agentName))
|
|
.orderBy(desc(xosAuditTrail.createdAt))
|
|
.limit(limit);
|
|
}
|
|
return db.select().from(xosAuditTrail).orderBy(desc(xosAuditTrail.createdAt)).limit(limit);
|
|
}
|
|
|
|
async getGovernanceStats(): Promise<{
|
|
totalContracts: number;
|
|
totalTools: number;
|
|
totalSkills: number;
|
|
totalPolicies: number;
|
|
totalAuditEntries: number;
|
|
recentDenials: number;
|
|
}> {
|
|
const [contracts] = await db.select({ count: sql<number>`count(*)` }).from(xosContractRegistry).where(eq(xosContractRegistry.isActive, true));
|
|
const [tools] = await db.select({ count: sql<number>`count(*)` }).from(xosToolRegistry).where(eq(xosToolRegistry.isActive, true));
|
|
const [skills] = await db.select({ count: sql<number>`count(*)` }).from(xosSkillRegistry);
|
|
const [policies] = await db.select({ count: sql<number>`count(*)` }).from(xosPolicyRules).where(eq(xosPolicyRules.isActive, true));
|
|
const [audit] = await db.select({ count: sql<number>`count(*)` }).from(xosAuditTrail);
|
|
const [denials] = await db.select({ count: sql<number>`count(*)` }).from(xosAuditTrail)
|
|
.where(and(eq(xosAuditTrail.decision, "denied"), sql`created_at > NOW() - INTERVAL '24 hours'`));
|
|
|
|
return {
|
|
totalContracts: Number(contracts.count),
|
|
totalTools: Number(tools.count),
|
|
totalSkills: Number(skills.count),
|
|
totalPolicies: Number(policies.count),
|
|
totalAuditEntries: Number(audit.count),
|
|
recentDenials: Number(denials.count),
|
|
};
|
|
}
|
|
}
|
|
|
|
export const governanceService = new GovernanceService();
|