Lewati ke isi

Audit Catalog — Action Reference

Generated 2026-04-24 after the full Phase 4 audit retrofit session.

Every row written to audit_log has an action field of the form <family>.<resource>.<verb>. This catalog documents every action HUPH writes today, the code path that writes it, and a sample SQL query for reconstructing the event in a compliance review or post-incident analysis.

Quick reference — action families

Family Source Includes before_state / after_state
auth.* apps/api/src/middleware/auditSensitiveAccess.ts no (auth events)
user.* apps/admin/src/app/api/settings/users/route.ts no (logged events)
cluster.* apps/admin/src/app/api/settings/clusters/route.ts yes (update/delete)
persona.config.update apps/admin/src/app/api/settings/persona/route.ts yes
guidance_rule.* apps/admin/src/app/api/settings/guidance-rules/[id]/route.ts yes
audience_rule.update apps/admin/src/app/api/settings/audience-rules/[id]/route.ts yes
config.update multiple: admin api/settings/config, api settings/guard, settings/faq-threshold yes
conversation.note.create apps/admin/src/app/api/conversations/notes/route.ts yes (afterState only)
monitoring.* apps/api/src/routes/monitoring.ts, apps/api/src/routes/adminUsageStats.ts no (widget opens)
faq.* apps/api/src/routes/faqLocal.ts yes (update/toggle/delete)
escalation_rule.* apps/api/src/routes/escalationRules.ts yes
intent.* apps/api/src/routes/intents.ts yes (update/toggle/delete)
campaign.* apps/api/src/routes/campaigns.ts yes (update/delete)
follow_up_rule.* apps/api/src/routes/followUp.ts yes
follow_up_queue.cancel apps/api/src/routes/followUp.ts yes
kb.* apps/api/src/routes/kb.ts no (Dify holds source of truth)
crawl_source.* apps/api/src/routes/crawl.ts yes

Action list

Auth (middleware-written)

  • api.auth.pass — authenticated request to a sensitive endpoint.
  • api.auth.fail — auth middleware rejected a request.
  • api.auth.warn — auth mode=warn, request passed but would-have-failed in enforce.
  • api.webhook.signature.pass|fail|warn — Zitadel action webhook HMAC check.

User actions (admin-side)

  • user.login — successful credentials login.
  • user.list.viewed — admin opened /settings/users.
  • user.create, user.update, user.role_change, user.deactivate.

Phase 4 diff-populating families

These write before_state + after_state JSONB so Phase 2 config history queries + the /audit page expanded view show structural diffs. beforeState: null means a create; afterState: null means a delete.

cluster

  • cluster.create (afterState)
  • cluster.update (before + after)
  • cluster.delete (before, afterState=null)

Snapshot fields: name, code, programs[].

persona.config.update

Snapshot fields: botName, botAvatarUrl, defaultTone, answerLength, emojiUsage, escalationContact, escalationEmail, registrationUrl (8 fields).

guidance_rule.*

  • guidance_rule.update (before + after)
  • guidance_rule.delete (before, afterState=null)

Snapshot: category, ruleText, priority, isActive.

audience_rule.update

Snapshot: userType, toneOverride, greetingTemplate, contentFocus, addressStyle, escalationTriggers, isActive (7 fields).

config.update (three writers share this action name)

  1. Admin-side /api/settings/config PATCH (single-key config edits). resource_id = the app_config key. Snapshot: { value }.
  2. API-side /api/v1/settings/guard PUT (11 guard keys). One audit row per changed key; skip unchanged keys.
  3. API-side /api/v1/settings/faq-threshold PUT (Phase 2 knob #1). Includes details.difyJobId for Dify async re-index correlation.

faq.*

  • faq.create (afterState — full snapshot)
  • faq.update (before + after)
  • faq.toggle (before + after — captures the activate/deactivate flip)
  • faq.delete (before, afterState=null)
  • faq.bulk_deactivate (aggregate — details.deactivated_count, details.dify_failed_ids)

Snapshot: question, answer, is_active, cluster_id, source, category, question_en, answer_en, has_dify_annotation (boolean).

escalation_rule.*

  • escalation_rule.create (afterState)
  • escalation_rule.update (before + after)
  • escalation_rule.toggle (before + after)
  • escalation_rule.delete (before, afterState=null)

Snapshot: name, description, conditions, action, priority, is_active, is_system, message_en.

intent.*

  • intent.create (afterState)
  • intent.update (before + after)
  • intent.toggle (before + after)
  • intent.delete (before, afterState=null)

Snapshot: slug, name, description, response_template, handler_type, priority, is_active, is_system.

campaign.*

  • campaign.create (afterState)
  • campaign.update (before + after)
  • campaign.delete (before, afterState=null)

Snapshot: name, code, phone, prefilled_text, is_active.

follow_up_rule.*

  • follow_up_rule.create (afterState)
  • follow_up_rule.update (before + after)
  • follow_up_rule.delete (before, afterState=null)

Snapshot: funnel_stage, attempt_number, delay_hours, template_name, template_body, template_body_en, template_params, is_active.

follow_up_queue.cancel

Snapshot: { status: 'pending' }{ status: 'failed', error_message: 'Cancelled by admin' }. details.conversationId + ruleId + scheduledFor for investigation.

crawl_source.*

  • crawl_source.create (afterState)
  • crawl_source.update (before + after)
  • crawl_source.delete (before, afterState=null — soft-delete)

Snapshot: name, url, schedule, status, strategy.

KB mutations (no before/after — Dify is source of truth)

  • kb.upload (details: filename, size, dataset)
  • kb.create_text (details: name, textLength)
  • kb.delete (details: datasetKey, datasetId)
  • kb.enable / kb.disable / kb.archive / kb.un_archive — status transitions
  • kb.segment_edit (details: documentId, contentLength, keywordsCount)

Conversation note

  • conversation.note.create (afterState: text, conversationId)

Monitoring widget reads

  • monitoring.judge.read — Bot Health judge widget open
  • monitoring.coverage.read — super_admin opened Feature Coverage

Sample compliance queries

All config changes this month:

SQL
SELECT created_at, admin_user_id, action, resource_id,
       before_state, after_state
FROM audit_log
WHERE (before_state IS NOT NULL OR after_state IS NOT NULL)
  AND created_at > now() - interval '30 days'
ORDER BY created_at DESC;

Uses partial index idx_audit_log_config_changes (scripts/migrate-audit-config-changes-index.sql).

Full history of one resource:

SQL
SELECT created_at, action, admin_user_id, before_state, after_state
FROM audit_log
WHERE resource_type = 'escalation_rule'
  AND resource_id = '<uuid>'
ORDER BY created_at DESC;

Uses partial index idx_audit_log_resource (scripts/migrate-audit-resource-index.sql).

Who deactivated that FAQ?

SQL
SELECT created_at, admin_user_id, before_state, after_state, ip_address
FROM audit_log
WHERE action = 'faq.toggle'
  AND resource_id = '<faq uuid>'
  AND (after_state->>'is_active')::boolean = false
ORDER BY created_at DESC
LIMIT 1;

Writer APIs

Two audit writers ship today:

  • apps/admin/src/lib/audit.ts::logAudit — Prisma-backed, for admin-side Next.js routes. Accepts request: Request for IP/UA/device/browser/session extraction via UAParser.

  • apps/api/src/audit/logFeatureAccess.ts::logFeatureAccess — Express-side, pg pool query. Accepts request: Request same way; logAuthEvent is a sibling for auth-specific events only.

Both support optional beforeState/afterState jsonb with three-state semantics: undefined leaves the column untouched, null writes SQL NULL explicitly, object writes the snapshot.

Coverage check

Run bash scripts/audit-coverage-report.sh [days] to see per-family activity counts. [dormant] rows either mean the feature has no traffic yet or the instrumentation was added recently — check against the /analytics-v2 Feature Coverage widget for the operator view.