UNPKG

overmind-mcp

Version:

Orchestrateur universel agents IA multi-modeles via MCP. Inclut le protocole 'Custom-Nickname' pour identifier vos agents avec des surnoms originaux (The Chaos Prophet, Shadow Sniper, etc.), l'isolation mémoire (Private Memory Context) et le support pour

121 lines • 5.57 kB
/** * ═══════════════════════════════════════════════════════════════════════════ * Audit Trail — Droit de rectification (art. 27) * ═══════════════════════════════════════════════════════════════════════════ * * Modifie un renseignement personnel (RP) inexact, incomplet ou périmé. * Le sujet désigne l'enregistrement via : * - son data_subject_id (scope de sécurité) * - le nom de la table ('agent_runs' | 'knowledge_chunks') * - l'ID de l'enregistrement * - le champ à rectifier ('prompt', 'result' ou 'text') * - la nouvelle valeur * * L'UPDATE est doublement filtrée par `id` ET `data_subject_id` pour éviter * qu'un sujet ne modifie les RP d'un autre (isolation forte). * * Chaque rectification est tracée dans access_log (action='write'). */ import { z } from 'zod'; import { getPool } from 'overmind-postgres-mcp'; import { logAccess } from '../lib/audit/guard.js'; // Allowlist stricte : empêche l'injection de noms de colonnes arbitraires const ALLOWED_FIELDS_BY_TABLE = { agent_runs: new Set(['prompt', 'result']), knowledge_chunks: new Set(['text']), }; export const auditRectificationSchema = z.object({ data_subject_id: z.string().min(1).describe('Identifiant pseudonymisé du sujet'), table_name: z .enum(['agent_runs', 'knowledge_chunks']) .describe('Table contenant le RP à rectifier'), record_id: z.string().min(1).describe('ID de l\'enregistrement à modifier'), field: z .string() .describe('Champ à rectifier (prompt, result pour agent_runs ; text pour knowledge_chunks)'), new_value: z.string().describe('Nouvelle valeur du champ'), }); export async function auditRectification(args) { try { // ── Validation stricte du champ autorisé ───────────────────────────────── const allowedFields = ALLOWED_FIELDS_BY_TABLE[args.table_name]; if (!allowedFields) { return { content: [ { type: 'text', text: `❌ Table non supportée : \`${args.table_name}\`. Tables autorisées : agent_runs, knowledge_chunks`, }, ], isError: true, }; } if (!allowedFields.has(args.field)) { return { content: [ { type: 'text', text: `❌ Champ non autorisé : \`${args.field}\` sur \`${args.table_name}\`.\n` + `Champs autorisés : ${Array.from(allowedFields).join(', ')}`, }, ], isError: true, }; } const pool = getPool(); // Double filtre id + data_subject_id pour isolation const res = await pool.query(`UPDATE ${args.table_name} SET ${args.field} = $1, updated_at = $4 WHERE id = $2 AND data_subject_id = $3 RETURNING id, ${args.field}`, [args.new_value, args.record_id, args.data_subject_id, Date.now()]); const affected = res.rowCount ?? 0; // ── Journalisation (art. 27 — preuve de la rectification) ──────────────── await logAccess({ dataSubjectId: args.data_subject_id, accessedBy: 'audit_rectification', action: 'write', resourceType: args.table_name, resourceId: args.record_id, purpose: `audit_rectification:${args.table_name}.${args.field}`, }).catch(() => { }); // non-bloquant if (affected === 0) { return { content: [ { type: 'text', text: `⚠️ **Aucun enregistrement modifié**\n\n` + `Aucun enregistrement avec id=\`${args.record_id}\` et data_subject_id=\`${args.data_subject_id}\` dans \`${args.table_name}\`.\n` + `_Vérifiez les IDs ou l'appartenance du record au sujet._`, }, ], }; } return { content: [ { type: 'text', text: `✏️ **Rectification appliquée** — Audit Trail (Loi 25 art. 27)\n\n` + `**Sujet :** \`${args.data_subject_id}\`\n` + `**Table :** \`${args.table_name}\`\n` + `**Record ID :** \`${args.record_id}\`\n` + `**Champ :** \`${args.field}\`\n` + `**Enregistrements affectés :** ${affected}\n\n` + `Nouvelle valeur :\n\`\`\`\n${args.new_value.slice(0, 1000)}${args.new_value.length > 1000 ? '\n…' : ''}\n\`\`\``, }, ], }; } catch (err) { return { content: [ { type: 'text', text: `❌ Erreur audit_rectification: ${err instanceof Error ? err.message : String(err)}`, }, ], isError: true, }; } } //# sourceMappingURL=audit_rectification.js.map