The question a Slack search can't answer
A user's role silently changes from ADMIN to USER. Nobody remembers approving it. The obvious places to look — Slack, email, memory — all fail the same way: they record intent, not action, and only when someone thought to write it down. What you actually need is a record the system itself keeps, independent of anyone remembering to document anything.
What gets recorded, and what doesn't
WKFGo's audit log is append-only — rows are only ever inserted, never updated or deleted, so a compromised admin account can't cover its tracks by editing history. It tracks governance-relevant actions specifically, not every click in the app:
const (
ActionLoginSuccess = "auth.login.success"
ActionLoginFailure = "auth.login.failure"
ActionLogin2FAPrompt = "auth.login.2fa_required"
Action2FAEnabled = "auth.2fa.enabled"
Action2FADisabled = "auth.2fa.disabled"
ActionUserCreated = "user.created"
ActionUserDeleted = "user.deleted"
ActionUserRoleChange = "user.role_changed"
ActionPermissionSet = "permission.changed"
ActionProjectDeleted = "project.deleted"
ActionTaskDeleted = "task.deleted"
ActionPlanChanged = "plan.changed"
)
That's a deliberate boundary. A log of every task edit and comment would be noise nobody reads — the entries that matter for governance are the ones with security or billing consequences: who logged in and from where, who turned 2FA off, who changed a role, who deleted a project. Each entry carries the actor, the action, what it targeted, a JSON metadata blob for action-specific detail, the source IP, and a timestamp — enough to reconstruct exactly what happened without guessing.
Scoped to your organization, not the whole platform
type AuditLog struct {
OrganizationID *uint `json:"organizationId,omitempty"`
ActorID *uint `json:"actorId,omitempty"`
Action string `json:"action"`
TargetType string `json:"targetType,omitempty"`
TargetID string `json:"targetId,omitempty"`
Metadata datatypes.JSON `json:"metadata,omitempty"`
IP string `json:"ip,omitempty"`
CreatedAt time.Time `json:"createdAt"`
}
An org admin querying the log only ever sees their own organization's rows — the same tenant boundary enforced everywhere else in WKFGo applies here too. A platform super-admin can opt into an all-organizations view for cross-tenant investigation, but that's a distinct, explicitly-elevated query path, not the default.
Reading the log: filters that matter
The query supports narrowing by actor, action, target type, and a date range — the shape you actually need when investigating something specific rather than scrolling a firehose. "Show me every permission change in the last 30 days" and "show me everything user 42 did on March 3rd" are both single filtered queries, not a manual scan through thousands of rows.
Common mistakes
Treating the audit log as a replacement for real-time alerting. It answers "what happened," reliably, after the fact. It doesn't page anyone when something happens — that's a separate concern (WKFGo's in-app notifications cover the "right now" case; the audit log covers "prove it later").
Expecting every action to be there. The log is intentionally narrow — task and comment edits aren't in it. If you need a full edit history for a specific task, that's task history, a different and much noisier record kept per-task, not per-organization.
Assuming a deleted user erases their trail. It doesn't — ActorID on past entries stays as-is even after the actor is gone, because the point of an audit log is that it survives the thing it's auditing.
FAQ
Can a regular team member see the audit log?
No — it's an admin-scoped view (ADMIN/SUPER_ADMIN on the organization), same tier that can change roles and permissions in the first place.
Does failed-login tracking help against brute-force attempts? It's a record, not a blocker — rate limiting is the thing that actually slows a brute-force attempt down; the audit log is where you go afterward to see which account was targeted and when.
Is the log itself ever purged? No retention job runs against it — unlike recycle-bin cleanup or old backup files, audit rows are meant to accumulate, not expire.