Skip to content

Admin Console

The admin frontend is a Next.js client for backend read paths, operation triage, support bundles, and governed seat revocation. The frontend route files live under seat-manager/apps/admin/src/app/(admin)/, and the backend admin API begins at /admin/v1.

Admin Pages

dashboard

The dashboard shows total seats, pending operations, failed operations, lifecycle watcher health, seat status counts, recently revoked seats, and recent failed audit entries. It calls fetchDashboard and fetchHealth from the page loader in seat-manager/apps/admin/src/app/(admin)/dashboard/page.tsx:16, which map to /admin/v1/dashboard and /admin/v1/health in seat-manager/apps/admin/src/lib/api.ts:339.

Both endpoints allow VIEWER, OPERATOR, and ADMIN sessions through getAdminSessionOrThrow in seat-manager/src/api/server.ts:2625 and seat-manager/src/api/server.ts:2670. There are no dashboard query parameters and no mutation controls, so CSRF and step-up do not apply. The health payload exposes stale processing count and the EL watcher cursor at seat-manager/src/api/server.ts:2649.

seats

The seats page shows lifecycle status, beneficiary, validator pubkey, vault address, principal target, pending-operation flag, failed-operation flag, revoked flag, active flag, and timestamps. The page exposes status and seat-id filters in seat-manager/apps/admin/src/app/(admin)/seats/page.tsx:11, and the API client supports status, has_pubkey, has_pending_operation, has_failed_operation, beneficiary, vault_address, validator_pubkey, seat_id, limit, and offset in seat-manager/apps/admin/src/lib/api.ts:347.

The backend endpoint is /admin/v1/seats, registered at seat-manager/src/api/server.ts:2715. It accepts the same filters, caps limit at 500, and returns items plus pagination metadata in seat-manager/src/api/server.ts:2717. Roles VIEWER, OPERATOR, and ADMIN can access it; there are no mutations on the list page, so no CSRF or step-up path is active.

seats/[id]

The seat detail page shows the seat object, lifecycle evidence, operation history, audit events, economics, contract evidence, and revocation controls. It loads /admin/v1/seats/:seatId and /admin/v1/seats/:seatId/events together from seat-manager/apps/admin/src/app/(admin)/seats/[id]/page.tsx:50. The API client functions are fetchSeat, fetchSeatEvents, and revokeSeat in seat-manager/apps/admin/src/lib/api.ts:378.

Read access is allowed for VIEWER, OPERATOR, and ADMIN in seat-manager/src/api/server.ts:2846 and seat-manager/src/api/server.ts:2935. The revoke form appears only for ADMIN and revocable statuses in seat-manager/apps/admin/src/app/(admin)/seats/[id]/page.tsx:71. The mutation requires CSRF, fresh step-up, governance ticket, reason code, note, idempotency key, and confirmation phrase REVOKE <seatId> in seat-manager/src/api/server.ts:2983 and seat-manager/apps/admin/src/app/(admin)/seats/[id]/page.tsx:116.

operations

The operations page shows queued, processing, proposed, submitted, confirmed, finalized, and failed operations, with kind and status filters. The frontend filters status and kind before calling fetchOperations in seat-manager/apps/admin/src/app/(admin)/operations/page.tsx:38, and the client maps those filters to /admin/v1/operations in seat-manager/apps/admin/src/lib/api.ts:402.

The list and detail endpoints allow VIEWER, OPERATOR, and ADMIN in seat-manager/src/api/server.ts:3151 and seat-manager/src/api/server.ts:3207. Retry and cancel are mutations, exposed through buttons on the operations page in seat-manager/apps/admin/src/app/(admin)/operations/page.tsx:86. Retry requires OPERATOR or ADMIN, CSRF, fresh step-up, an idempotency key, and status failed or proposed in seat-manager/src/api/server.ts:3260. Cancel requires OPERATOR or ADMIN, CSRF, fresh step-up, an idempotency key, and status queued, processing, or proposed in seat-manager/src/api/server.ts:3333.

audit

The audit page shows timestamp, action, actor, result, target, request id, and detail. It exposes seat id, action, and result filters in seat-manager/apps/admin/src/app/(admin)/audit/page.tsx:17, and the API client also supports actor, date range, limit, and offset in seat-manager/apps/admin/src/lib/api.ts:457.

The backend endpoint is /admin/v1/audit at seat-manager/src/api/server.ts:3406. Roles VIEWER, OPERATOR, and ADMIN can access it. The endpoint filters by seat_id, actor, action, result, date_from, date_to, limit, and offset in seat-manager/src/api/server.ts:3408. It is read-only, so no CSRF or step-up applies.

support

The support page fetches a redacted per-seat support bundle. It takes a seat id, calls fetchSupportBundle, and renders seat public identifiers, economics, lifecycle summary, operation summary, audit summary, and config facts in seat-manager/apps/admin/src/app/(admin)/support/page.tsx:14.

The backend endpoint is /admin/v1/support/bundle/:seatId, registered at seat-manager/src/api/server.ts:3460. Roles VIEWER, OPERATOR, and ADMIN can access it. It has no query parameters, no mutations, and no CSRF or step-up requirement. The response joins seat state, lifecycle evidence, operations, audit events, economics, and non-secret config facts in seat-manager/src/api/server.ts:3467.

database

The database page is an intentional placeholder. It renders a message that direct database browsing is unavailable in this admin build at seat-manager/apps/admin/src/app/(admin)/database/page.tsx:1. It consumes no backend endpoint, accepts no query parameters, and exposes no mutation. Route access is controlled by the admin layout session gate, not by a page-specific backend role check.

drift

The drift page is an intentional placeholder. It states that drift analytics are not exposed as a dedicated backend endpoint in the current V2 runtime at seat-manager/apps/admin/src/app/(admin)/drift/page.tsx:1. It consumes no backend endpoint, accepts no query parameters, and exposes no mutation. The launch-grade multi-source quorum and durable reorg replay gap remains tracked in seat-manager/README.md:73.

operators

The operators page is an intentional placeholder. It states that operator directory data is not wired to the V2 admin backend at seat-manager/apps/admin/src/app/(admin)/operators/page.tsx:1. It consumes no backend endpoint, accepts no query parameters, and exposes no mutation. Operator identity is represented today by Seat.operatorId and audit fields in seat-manager/prisma/schema.prisma:10 and seat-manager/prisma/schema.prisma:115.

settings

The settings page is an intentional placeholder for backend-supported controls only. It states that unsafe manager toggles are not exposed in this build at seat-manager/apps/admin/src/app/(admin)/settings/page.tsx:1. It consumes no backend endpoint, accepts no query parameters, and exposes no mutation. Runtime settings still come from environment parsing in seat-manager/src/config/loader.ts:100.

users

The users page is an intentional placeholder. It states that admin user directory and RBAC management are not wired in this V2 runtime at seat-manager/apps/admin/src/app/(admin)/users/page.tsx:1. Current admin users are loaded from SEAT_MANAGER_ADMIN_USERS_JSON, parsed and validated in seat-manager/src/config/loader.ts:113 and seat-manager/src/config/loader.ts:154. There is no page mutation for creating or revoking users.

workflows

The workflows page is an intentional placeholder. It states that workflow orchestration controls are not exposed by the current V2 backend at seat-manager/apps/admin/src/app/(admin)/workflows/page.tsx:1. Existing workflows are backend routes and workers: operation retry/cancel in seat-manager/src/api/server.ts:3260, lifecycle watching in seat-manager/src/watchers/lifecycleWatchLoop.ts:333, and operation execution in seat-manager/src/watchers/operationWorker.ts:135.

Session Lifecycle

Login starts with POST /admin/v1/login. The frontend calls login, stores the returned CSRF token, and relies on the server-set cookie in seat-manager/apps/admin/src/lib/api.ts:302. The backend validates the username and password against configured users, writes an AdminSession row, creates a CSRF token, signs the session token, and sets the cookie in seat-manager/src/api/adminAuth.ts:161 and seat-manager/src/api/server.ts:2465.

Session refresh is a touch on GET /admin/v1/session. The backend reads the cookie, verifies the signed claims, checks the persisted session row, rejects revoked or expired sessions, extends the expiry when touch is true, and signs a fresh cookie in seat-manager/src/api/adminAuth.ts:196 and seat-manager/src/api/server.ts:2429. SEAT_MANAGER_ADMIN_SESSION_TTL_SECONDS defaults to 43_200, the cookie name defaults to sm_admin_session, and both values flow into runtime config in seat-manager/src/config/loader.ts:115 and seat-manager/src/config/loader.ts:150.

Step-up is a separate password re-check for dangerous writes. The frontend calls POST /admin/v1/step-up with CSRF in seat-manager/apps/admin/src/lib/api.ts:330. The backend rate-limits step-up attempts, creates a replacement session with the same CSRF token, sets stepUpExpiresAtMs, revokes the previous session row, and returns the new expiry in seat-manager/src/api/adminAuth.ts:274. SEAT_MANAGER_ADMIN_STEP_UP_TTL_SECONDS defaults to 600, and the step-up rate limiter defaults to 6 attempts per 60_000 milliseconds in seat-manager/src/config/loader.ts:120 and seat-manager/src/config/loader.ts:135.

Logout is server-side session revocation plus client-side cache clearing. The frontend sends POST /admin/v1/logout with CSRF and clears local API session state in seat-manager/apps/admin/src/lib/api.ts:321. The backend verifies CSRF when an existing session is present, revokes the persisted session, clears the cookie, and emits admin.auth.logout in seat-manager/src/api/server.ts:2531.

Session revocation by another admin is represented by AdminSession.revoked_at and replaced_by_session_id in seat-manager/prisma/schema.prisma:57. Every session read checks the persisted row and rejects revoked sessions in seat-manager/src/api/adminAuth.ts:207. Step-up also revokes the previous session and records the replacement id in seat-manager/src/api/adminAuth.ts:325.

Revocation Runbook

  1. Confirm the seat and validator state. Open the seat detail page, verify the lifecycle status, read operations, and check economics; the backend loads seat, lifecycle evidence, operations, audit events, evidence, and economics in seat-manager/src/api/server.ts:2846.
  2. Check for in-flight work. Use the operations page or /admin/v1/operations?seat_id=<seat_id> to find queued, processing, proposed, submitted, or confirmed rows; the endpoint filters by seat id, status, kind, limit, and offset in seat-manager/src/api/server.ts:3151.
  3. Confirm validator activity. The seat detail economics comes from the same status economics builder used by operator status in seat-manager/src/api/server.ts:2858. If the validator is still attesting, revocation records backend authority only; it does not exit the validator.
  4. Obtain governance approval evidence from the governance process. The backend requires a governance ticket and verifies approval id, hash, issuer, expiry, verified-at timestamp, and nonce before transition in seat-manager/src/api/server.ts:3041.
  5. In the admin UI, open Seats -> <seat_id>, enter governance ticket, reason code, and audit note. The form schema requires all three fields in seat-manager/apps/admin/src/lib/schemas.ts:12.
  6. Click review, complete step-up, and type the confirmation phrase REVOKE <seat_id>. The frontend sends exactly that phrase in seat-manager/apps/admin/src/app/(admin)/seats/[id]/page.tsx:116, and the backend rejects anything else in seat-manager/src/api/server.ts:2995.
  7. The backend first moves non-active/non-exception statuses to EXCEPTION with admin_revoke_requested evidence, then moves EXCEPTION or ACTIVE to REVOKED with governance evidence in seat-manager/src/api/server.ts:3049 and seat-manager/src/api/server.ts:3060. Those transitions match the state-machine specs in seat-manager/src/workflow/stateMachine.ts:81.
  8. Record the warning: revocation does not reverse the on-chain deposit and does not recover the validator BLS key. The response body says on-chain state was not reversed in seat-manager/src/api/server.ts:3102.
  9. Notify the operator with the new status, the fact that the old seat is terminal, and the re-enrollment path. A revoked seat no longer blocks a new seat for the same operator because the application rule and database index exclude REVOKED in seat-manager/src/onboarding/store.ts:19 and seat-manager/prisma/migrations/20260514165745_one_active_seat_per_operator/migration.sql:37.
  10. Verify closure by opening the audit page filtered to admin.seat.revoke, checking the lifecycle evidence for REVOKED, and confirming no live operations remain for the old seat. The success audit event is emitted in seat-manager/src/api/server.ts:3134.

Post-revocation review has four required checks:

  • Seat.status is REVOKED and the previous status is visible in the revoke response fields built at seat-manager/src/api/server.ts:3102.
  • The lifecycle evidence row contains governance_approval_hash, governance_approval_nonce, reason_code, and actor; those keys are written in seat-manager/src/api/server.ts:3064.
  • The audit log contains exactly one success event for the admin request id and reason code; the event fields are written in seat-manager/src/api/server.ts:3134.
  • The operator is not sent a new JWT until the old seat is revoked, because provisioning a second non-revoked seat fails at seat-manager/src/onboarding/prismaStore.ts:296.
  • The support bundle for the old seat still loads, which proves retained seat, lifecycle, operation, and audit history through seat-manager/src/api/server.ts:3467.