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)
- Admin-side
/api/settings/configPATCH (single-key config edits).resource_id= the app_config key. Snapshot:{ value }. - API-side
/api/v1/settings/guardPUT (11 guard keys). One audit row per changed key; skip unchanged keys. - API-side
/api/v1/settings/faq-thresholdPUT (Phase 2 knob #1). Includesdetails.difyJobIdfor 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 transitionskb.segment_edit(details: documentId, contentLength, keywordsCount)
Conversation note
conversation.note.create(afterState: text, conversationId)
Monitoring widget reads
monitoring.judge.read— Bot Health judge widget openmonitoring.coverage.read— super_admin opened Feature Coverage
Sample compliance queries
All config changes this month:
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:
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?
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. Acceptsrequest: Requestfor IP/UA/device/browser/session extraction via UAParser. -
apps/api/src/audit/logFeatureAccess.ts::logFeatureAccess— Express-side, pg pool query. Acceptsrequest: Requestsame way;logAuthEventis 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.