import type { Service, Contrat, Champ, Action, ActionLogin, Membre, Pair, } from '../types/contract'; import type { LoginPath, SignatureRequirement } from '../types/identity'; /** * Cache local pour les objets du graphe. */ interface GraphCache { services: Map; contrats: Map; champs: Map; actions: Map; membres: Map; pairs: Map; } /** * Service de résolution du graphe contractuel. */ export class GraphResolver { private cache: GraphCache; constructor() { this.cache = { services: new Map(), contrats: new Map(), champs: new Map(), actions: new Map(), membres: new Map(), pairs: new Map(), }; } /** * Add a service to the cache. */ addService(service: Service): void { this.cache.services.set(service.uuid, service); } /** * Add a contrat to the cache. */ addContrat(contrat: Contrat): void { this.cache.contrats.set(contrat.uuid, contrat); } /** * Add a champ to the cache. */ addChamp(champ: Champ): void { this.cache.champs.set(champ.uuid, champ); } /** * Add an action to the cache. */ addAction(action: Action): void { this.cache.actions.set(action.uuid, action); } /** * Add a membre to the cache. */ addMembre(membre: Membre): void { this.cache.membres.set(membre.uuid, membre); } /** * Add a pair to the cache. */ addPair(pair: Pair): void { this.cache.pairs.set(pair.uuid, pair); } /** * Get all services from cache. */ getServices(): Service[] { return Array.from(this.cache.services.values()); } /** * Get all membres from cache. */ getMembres(): Membre[] { return Array.from(this.cache.membres.values()); } /** * Get all contrats from cache. */ getContrats(): Contrat[] { return Array.from(this.cache.contrats.values()); } /** * Get all pairs from cache. */ getPairs(): Pair[] { return Array.from(this.cache.pairs.values()); } /** * Get all actions from cache. */ getActions(): Action[] { return Array.from(this.cache.actions.values()); } /** * Resolve login path: Service → Contrat → Champ → ActionLogin → Membre → Pair. */ resolveLoginPath( serviceUuid: string, membreUuid: string, ): LoginPath | null { const service = this.cache.services.get(serviceUuid); if (service === undefined) { return null; } const contrat = this.cache.contrats.get(service.contrat_uuid); if (contrat === undefined) { return { service_uuid: serviceUuid, contrat_uuid: [], action_login_uuid: '', membre_uuid: membreUuid, pairs_attendus: [], signatures_requises: [], statut: 'incomplet', }; } const contratVersion = contrat.version; const champs = Array.from(this.cache.champs.values()).filter((c) => c.contrats_parents_uuid.includes(service.contrat_uuid), ); const membre = this.cache.membres.get(membreUuid); if (membre === undefined) { return { service_uuid: serviceUuid, contrat_uuid: [service.contrat_uuid], contrat_version: contratVersion, champ_uuid: champs.map((c) => c.uuid), action_login_uuid: '', membre_uuid: membreUuid, pairs_attendus: [], signatures_requises: [], statut: 'incomplet', }; } const actionLogin = this.findActionLogin(membre.actions_parents_uuid); if (actionLogin === null) { return { service_uuid: serviceUuid, contrat_uuid: [service.contrat_uuid], contrat_version: contratVersion, champ_uuid: champs.map((c) => c.uuid), action_login_uuid: '', membre_uuid: membreUuid, pairs_attendus: [], signatures_requises: [], statut: 'incomplet', }; } const pairsAttendus = Array.from(this.cache.pairs.values()) .filter((p) => p.membres_parents_uuid.includes(membreUuid)) .map((p) => p.uuid); const signaturesRequises = this.computeRequiredSignatures( actionLogin, membreUuid, ); const statut = pairsAttendus.length > 0 && signaturesRequises.length > 0 ? 'complet' : 'incomplet'; return { service_uuid: serviceUuid, contrat_uuid: [service.contrat_uuid], contrat_version: contratVersion, champ_uuid: champs.map((c) => c.uuid), action_login_uuid: actionLogin.uuid, membre_uuid: membreUuid, pairs_attendus: pairsAttendus, signatures_requises: signaturesRequises, statut, }; } /** * Find action login from action UUIDs. */ private findActionLogin(actionUuids: string[]): ActionLogin | null { for (const uuid of actionUuids) { const action = this.cache.actions.get(uuid); if (action !== undefined && this.isActionLogin(action)) { return action as ActionLogin; } } return null; } /** * Check if an action is a login action. */ private isActionLogin(action: Action): boolean { return ( action.types.types_names_chiffres.includes('login') || action.types.types_uuid.some((uuid) => uuid.includes('login')) ); } /** * Compute required signatures from validators. */ private computeRequiredSignatures( action: Action, membreUuid: string, ): SignatureRequirement[] { const requirements: SignatureRequirement[] = []; for (const membreRole of action.validateurs_action.membres_du_role) { if (membreRole.membre_uuid === membreUuid) { for (const sigReq of membreRole.signatures_obligatoires) { requirements.push({ membre_uuid: sigReq.membre_uuid, pair_uuid: undefined, cle_publique: sigReq.cle_publique, cardinalite_minimale: sigReq.cardinalite_minimale, }); } } } return requirements; } /** * Validate that all required parents exist (at least 1 constraint). */ validateParentsConstraints(): { valid: boolean; errors: string[]; } { const errors: string[] = []; for (const champ of this.cache.champs.values()) { if (champ.contrats_parents_uuid.length === 0) { errors.push(`Champ ${champ.uuid} has no parent contrat`); } } for (const action of this.cache.actions.values()) { if (action.contrats_parents_uuid.length === 0) { errors.push(`Action ${action.uuid} has no parent contrat`); } } for (const membre of this.cache.membres.values()) { if (membre.actions_parents_uuid.length === 0) { errors.push(`Membre ${membre.uuid} has no parent action`); } } for (const pair of this.cache.pairs.values()) { if (pair.membres_parents_uuid.length === 0) { errors.push(`Pair ${pair.uuid} has no parent membre`); } } return { valid: errors.length === 0, errors, }; } }