Summary
Ports the auditing system from ../atlas and lands an admin-only /audit page in Hive, with a Hive-flavored extension for agent actors built around our Condukt workflows.
Why this shape: I wanted parity with Atlas’s data model and UX so we can reason about events the same way across both apps, but Hive’s call sites are different — single-tenant deployment, no executive/employee distinction, MCP- and webhook-heavy, with Condukt-driven agents that act on their own. So the port keeps Atlas’s Hive.Audit context + Activity schema + Noora table almost verbatim and adds a polymorphic actor_kind column on top so an agent run is a first-class kind of actor rather than buried in metadata.
Approaches considered and not taken:
- A separate
agents table for stronger identity per agent. Rejected for now: keying off actor_name is enough until we have many named agents and want “list all activity by IssueTriageAgent” as a real screen.
- Reusing the email-domain-derived org role (
Hive.Auth.role/1) instead of introducing a stored :role. Rejected: org membership and “can see the audit trail” are different concerns; mixing them would force every org member into admin.
- Setting
Audit.put_context/1 in RequireAuthenticated. Rejected: that plug only runs for the / PageController, and adding context there caused a regression in existing tests since the plug must also work for un-fetched-session conns. Context lives in DashboardLive.Hooks (LiveViews) and MCPAuthentication (MCP) instead, both of which already touch the user.
Key pieces:
Hive.Audit + Hive.Audit.Activity — process-dictionary actor/interface context, log/2 / record/3 / list_activities/1 / serialize/1. Interfaces are dashboard | mcp | webhook | worker | system. resource_path/3 only emits a path when one resolves (/specs/<number> and /meadows/<id> today), so we don’t show dead links.
- Agent actor model —
actor_kind column (user | agent | system), Hive.Audit.agent_actor(name, model:), and Hive.Agents.Sessions.run/3 wraps every Condukt call in Audit.with_context so events recorded inside an agent’s tools attribute to the agent. The agent’s model lands in metadata["agent_model"].
Hive.Audit.Policy (LetMe) + a new stored :role (:admin / :member) on Hive.Accounts.User. AGENTS.md now documents LetMe as the project’s authorization framework and the role distinction.
HiveWeb.AuditLive at /audit with a Noora table, filter dropdown (interface, kind, action, target), search, and pagination. Sidebar link is gated on admin?. Actor and Kind live in separate columns so the badge renders inside its own cell padding.
- MCP tools
list_audit_activities and get_audit_activity mirror the LiveView’s filters and gate on the same policy.
- Wiring —
Audit.record/3 calls at sign-in/out and at spec create/update for an immediately visible trail.
Verification:
- Promoted
test@hive.dev to admin in seeds and ran the seeds to produce 14 demo audit rows spanning every interface, two of which are agent-acted. Loaded /audit in the browser via /dev/login; the table renders, filters work, links resolve.
Testing
mix precommit (compile –warnings-as-errors, deps.unlock –unused, format, credo, test) — clean, 333 tests, 0 failures
mix run priv/repo/seeds.exs — idempotent re-run produces the demo audit dataset (entries keyed by seed_key in metadata so reruns replace rather than duplicate)
- Smoked
/audit in dev as test@hive.dev (admin) to verify the layout, the kind badge column, the spec/meadow target links, and the filter toolbar