Audit log

Fire Tools keeps a privacy-first, client-side audit log of meaningful user actions (creating/editing/deleting assets, running the FIRE calculation, changing settings, importing/exporting data). It exists so a user can answer "what did I just change?" — it is never transmitted off device in the pure-web build.

Design goals

Moving parts

Layer File Responsibility
Type contract src/types/auditLog.ts AuditActionType union, AUDIT_ACTION_TYPES, AuditLogEntry, isAuditActionType guard
Storage src/utils/cookieStorage.ts saveAuditLog / loadAuditLog / clearAuditLog, capping + byte-budget trim, AES via the existing cookie model
State src/contexts/AuditLogContext.tsx AuditLogProvider + useAuditLog(){ entries, logAuditEvent, clearLog }
UI src/components/SettingsPage.tsx Collapsible Audit log panel: filter by action/date, expandable rows, clear
Contract (server) docs/api/openapi.yaml, docs/database/schema.sql, server/migrations/0003_audit_log.* AuditLogEntry schema, audit_log table mirroring the union

The hook

import { useAuditLog } from '../contexts/AuditLogContext';

const { logAuditEvent } = useAuditLog();

// Record an action. Payload values must be primitive (string/number/boolean).
logAuditEvent('CREATE_ASSET', { assetClass: asset.assetClass, assetId: asset.id });

logAuditEvent:

Entries are persisted with a useEffect on the entry list (the initial hydration render is skipped) so the encrypted write happens once per change.

Action types

CREATE_ASSET, UPDATE_ASSET, DELETE_ASSET, RUN_CALCULATION, UPDATE_SETTINGS, IMPORT_DATA, EXPORT_DATA, CLEAR_DATA.

This union is the single source of truth. If you add a value, update all four places to keep the contract in sync:

  1. src/types/auditLog.ts (the union + AUDIT_ACTION_TYPES)
  2. docs/api/openapi.yaml (AuditActionType enum)
  3. docs/database/schema.sql (audit_log.action_type CHECK (... IN ...))
  4. server/migrations/0003_audit_log.up.sql (same CHECK)

…and add an i18n label under auditLog.actions.* in all five locales.

Where actions are wired

The Net-worth Sankey view is a passive visualization, not a user action, so it is intentionally not audited.

Why not instrument everything? The log is a UX affordance, not telemetry. We cover the main flows cleanly rather than emitting noise.

Privacy & retention

Testing