From 0e0cf8d90b3dfc86d2d77cc86bf02f12f3a9920b Mon Sep 17 00:00:00 2001 From: Ronni Baslund Date: Sun, 24 May 2026 22:20:50 +0200 Subject: [PATCH] feat(audit): record before/after diff for partner updates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit partner.updated events previously recorded only the field names that changed (metadata.changes). Now they record metadata.diff — a { field: { from, to } } map — by reading the partner before the findOneAndUpdate and comparing serialized values. Only fields that actually differ make it into the diff, so a save-without-changes records an empty diff instead of every DTO key. The operator audit row's expanded panel renders the diff as a small inline table (field · from → to). Older audit rows that still carry metadata.changes fall back to the original chip layout so historical events stay readable. --- apps/operator/pages/audit.vue | 55 ++++++++++++++++++- .../src/partners/partners.service.ts | 44 +++++++++++---- 2 files changed, 88 insertions(+), 11 deletions(-) diff --git a/apps/operator/pages/audit.vue b/apps/operator/pages/audit.vue index bae9bb5..8060ea2 100644 --- a/apps/operator/pages/audit.vue +++ b/apps/operator/pages/audit.vue @@ -92,6 +92,26 @@ function formatMetaValue(v: unknown): string { return String(v) } +// Compact human-readable rendering of a single side of a diff. Objects collapse +// to JSON; null / undefined / empty string render as a dim em-dash so the +// "before" side of a freshly populated field reads as clearly empty. +function formatDiffValue(v: unknown): string { + if (v === null || v === undefined || v === '') return '—' + if (typeof v === 'object') return JSON.stringify(v) + return String(v) +} + +// `metadata.diff` shape for partner.updated (and future *.updated actions): +// { fieldName: { from, to } }. Type-narrow safely; old audit rows that only +// carried `metadata.changes: string[]` are still rendered as plain chips by +// the fallback branch in the template. +interface DiffEntry { from: unknown; to: unknown } +function diffEntries(meta: Record | undefined): [string, DiffEntry][] { + const d = meta?.diff + if (!d || typeof d !== 'object') return [] + return Object.entries(d as Record) +} + // ── Tamper-evidence (Phase 3) + cold-storage archives (Phase 4) ──────── interface VerifyReport { ok: boolean @@ -273,7 +293,23 @@ function fmtRelative(iso: string | null | undefined): string { -
+ + + + + + + + + + +
{{ field }}{{ formatDiffValue(entry.from) }}{{ formatDiffValue(entry.to) }}
+ +