All files / auth ability.factory.ts

100% Statements 21/21
100% Branches 6/6
100% Functions 3/3
100% Lines 18/18

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 831x 1x                                                                 1x                                   1x     1x   5x   5x 3x     2x 2x   2x   2x     5x       1x 13x 5x 3x   2x    
import { Injectable } from '@nestjs/common';
import { AbilityBuilder, createMongoAbility, subject, type ForcedSubject, type MongoAbility } from '@casl/ability';
 
export type AgentToolRiskLevel = 'low' | 'medium' | 'high' | 'critical';
 
export interface AgentToolDefinition {
  name: string;
  riskLevel: AgentToolRiskLevel;
  description: string;
}
 
type AgentToolInstance = AgentToolDefinition & ForcedSubject<'AgentTool'>;
 
export type Action = 'manage' | 'create' | 'read' | 'update' | 'delete' | 'invoke';
export type Subject =
  | 'Organization'
  | 'Transaction'
  | 'Document'
  | 'Membership'
  | 'Goal'
  | 'Decision'
  | 'Observation'
  | 'Opportunity'
  | 'AgentTool'
  | AgentToolInstance
  | 'all';
export type AppAbility = MongoAbility<[Action, Subject]>;
export type MembershipRole = 'owner' | 'admin' | 'member';
 
export interface MembershipContext {
  tenantId: string;
  role: MembershipRole;
}
 
export const AGENT_TOOL_REGISTRY: readonly AgentToolDefinition[] = [
  {
    name: 'economic-data',
    riskLevel: 'low',
    description: 'World Bank/ECB/Eurostat/FRED/OECD, stiri, IP/domeniu lookups',
  },
  {
    name: 'capability-reasoning',
    riskLevel: 'low',
    description: 'Potrivire ocupatii/skill-uri ESCO pentru "ce poate/nu poate face agentul"',
  },
  {
    name: 'legislation-search',
    riskLevel: 'medium',
    description: 'Cautare semantica in legislatia UK/DE/RO -- informativ, nu consultanta juridica',
  },
];
 
const DEFAULT_ROLE_INVOKABLE_RISK_LEVELS: readonly AgentToolRiskLevel[] = ['low', 'medium'];
 
@Injectable()
export class AbilityFactory {
  createForMembership(membership: MembershipContext): AppAbility {
    const { can, build } = new AbilityBuilder<AppAbility>(createMongoAbility);
 
    if (membership.role === 'owner' || membership.role === 'admin') {
      can('manage', 'all');
    } else {
      // member: read all, write own documents/transactions, manage own strategic data
      can('read', 'all');
      can(['create', 'update'], ['Transaction', 'Document']);
      // CEO-OS is a personal management tool — members own their strategic data
      can(['create', 'update', 'delete'], ['Goal', 'Decision', 'Observation', 'Opportunity']);
      // Agent tools: low/medium risk only; high/critical reserved for owner/admin
      can('invoke', 'AgentTool', { riskLevel: { $in: [...DEFAULT_ROLE_INVOKABLE_RISK_LEVELS] } });
    }
 
    return build();
  }
}
 
export function canInvokeAgentTool(ability: AppAbility, toolName: string): boolean {
  const toolDefinition = AGENT_TOOL_REGISTRY.find((tool) => tool.name === toolName);
  if (!toolDefinition) {
    return false;
  }
  return ability.can('invoke', subject('AgentTool', toolDefinition));
}