Lesson 17 · The Admin Panel · Deeper Track

The operator console

How humans see and steer the graph — safely. Auth, audit, and Cypher-backed views. ~11 min.

Builds on: L14 · L12 New: RBAC + auth0 New: audit logging

In L14 we named the admin-panel as the real human-facing read surface but didn't open it. It's the operator console — a multi-feature internal web UI over everything you've built: explore the graph, investigate an address, manage focus tokens and IAM, edit rules. Because it gives privileged humans direct access to a risk system, its defining features aren't the views — they're the access control and the audit trail wrapped around them.

What it actually contains (orientation)
A console of features (each a server-rendered, Cypher-backed view): a graph explorer / viz (graph_viz_edges.go), an investigator (investigator.go), node lookup (lookup.go), IAM / roles (iam_resolver.go), focus-token admin, rule editing, plus team/CRM tooling. Don't memorise the list — internalise the shape: many small features sharing one auth + audit + render spine.

1 · Every request runs an auth gauntlet (RBAC)

This is the heart of it. The panel uses auth0 for identity (who are you?) and a permission guard for authorization (are you allowed this?). Each route declares the permission it needs (cmd/admin-panel/auth.go):

// cmd/admin-panel/auth.go — permissions are fine-grained strings
permGraphRead        = "risk_graph:graph:read"
permFocusTokensRead  = "risk_graph:focus_tokens:read"
permFocusTokensWrite = "risk_graph:focus_tokens:write"   // write ≠ read — separate perm

// routes → required permission (by prefix + HTTP method)
{prefix: "/api/admin/focus-tokens", methodPerm: {"GET": permFocusTokensRead, "POST": permFocusTokensWrite}}
{prefix: "/api/token/",           methodPerm: {"": permGraphRead}}

A middleware enforces it on every request:

request
operator + path
bypassAuth?
public paths (health/login) skip
auth0 session
identity (who)
permGate(guard)
has requiredPermission(method,path)? else 403
handler
Cypher read/write
// permGate wraps every handler; requiredPermission(method, path) → the needed perm
func permGate(g *authz.Guard) func(http.Handler) http.Handler { /* 403 if missing */ }
🔗 L14's "auth lives on the read side", made concrete
Recall the write path (L4·L9) never faces a user — but the panel does, so it's where authn/authz live. Note the read/write permission split (focus_tokens:read vs :write): viewing data and changing it are different grants. Least-privilege by construction.

2 · Every privileged action is audited

Identity + permission isn't enough for a tool that can change a live risk system — you also need to know who did what. The panel writes an audit log of operator actions to a per-operator Redis stream (cmd/admin-panel/auth0/xpapi_audit.go):

// xpapi:audit:{operatorEmail} — a Redis stream, one per operator
func (a *RedisAuditor) Record(ctx, operatorEmail string, evt AuditEvent)   // append on each action
func (a *RedisAuditor) Recent(ctx, operatorEmail string, limit int) ([]AuditEvent, error) // read back for display

Privileged routes (the audited "xpapi" — extended API — surface, xpapi_routes.go) record an AuditEvent per action, and Recent surfaces a history in the UI. Auth says you can; audit records that you did.

Why this rigor — your instinct is right
This is a tool that can mint focus tokens into the monitored set, edit IAM, change rules — actions that move what the whole system watches and alerts on. That's exactly the surface you'd want gated and logged. The same "privileged action = needs an audit trail" instinct you'd apply to a treasury multisig or a protocol admin panel. Notice the auditor even reuses a Redis stream — the same primitive as blocks (L7) and alerts (L15). One toolbox, everywhere.

3 · The views: server-rendered, Cypher-backed reads

Behind the auth gauntlet, a feature is a controller that runs an anchored Cypher read and renders HTML. It obeys the L2 rules you know: graph_id-scoped, anchored at a known id, bounded — never a full scan (this is a 1.8M-node graph, L14). The graph-viz feature is the most graph-shaped: it pulls a node's neighbourhood as edges to draw, deliberately excluding super-node and analytical edges so the picture stays legible.

A nice discipline: features are self-documenting
Per the repo's CLAUDE.md, every Cypher-backed admin-panel feature ships a doc (cmd/admin-panel/docs/<section>/<slug>.md) with a mermaid diagram, a "renders-on" table, the actual Cypher, and an external-deps table — and tests reject a feature whose doc has dangling links or an orphaned data-doc-slug. The UI and its documentation are kept in lockstep by CI. (Same "enforce the invariant mechanically" spirit as L9's forbidigo guard.)

4 · Where it sits in the whole system

The admin-panel is a pure consumer: it reads the graph (and its pre-computed risk fields, L13) and lets operators perform bounded privileged writes — which, like all bulk writes, still flow through the graph-writer (L9); the panel doesn't bypass the single-writer discipline. It adds no new data-plane mechanism — it's the human control plane over the machine you've spent sixteen lessons understanding.

The recurring toolbox, one last time
Look what the panel is built from: Redis streams (audit, L7/L15), anchored Cypher (L2), pre-computed fields (L13), the graph-writer for writes (L9), CI-enforced invariants (L9). Nothing new — the same patterns, recombined into a human interface. That's the sign you now understand the system: every new corner is assembled from parts you already know.

Check yourself

1. The admin-panel is, in one phrase…
2. auth0 vs the permission guard — what's the division of labour?
3. Why are focus_tokens:read and focus_tokens:write separate permissions?
4. What does the per-operator xpapi:audit:{email} Redis stream provide?
5. A controller rendering a node's neighbourhood must follow which rule from L2?
6. When an operator changes focus tokens via the panel, the actual graph write…
7. The CI rule that rejects an admin-panel feature whose doc has dangling links is the same spirit as…
8. The deeper takeaway from the admin-panel is…
↳ Ask your teacher
Try: "Show me requiredPermission's route table," · "How does the graph-viz controller build its Cypher?" · "What's in an AuditEvent?" · "How does auth0 map a user to permissions (the Guard)?"

What you can now do

🏁 Effectively the whole codebase
Data plane (L1–L11) · analytics (L6·L13·L16) · product/consumer side (L12·L14·L15) · human control plane (L17). You've now opened every major corner of risk-graph-indexer — and crucially, you can tell when a "new" part is just old patterns recombined. That's mastery of a system, not just familiarity.

Grounded in: cmd/admin-panel/auth.go (permission constants, route→perm table, bypassAuth, requiredPermission, permGate), cmd/admin-panel/auth0/xpapi_audit.go (RedisAuditor Record/Recent, xpapi:audit:{email} stream), graph_viz_edges.go / investigator.go / lookup.go / iam_resolver.go (features), CLAUDE.md (admin-panel doc-per-feature requirement). Verify against source — the code is the truth.