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¶
- 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. - 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 inseat-manager/src/api/server.ts:3151. - 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. - 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. - In the admin UI, open
Seats -> <seat_id>, enter governance ticket, reason code, and audit note. The form schema requires all three fields inseat-manager/apps/admin/src/lib/schemas.ts:12. - Click review, complete step-up, and type the confirmation phrase
REVOKE <seat_id>. The frontend sends exactly that phrase inseat-manager/apps/admin/src/app/(admin)/seats/[id]/page.tsx:116, and the backend rejects anything else inseat-manager/src/api/server.ts:2995. - The backend first moves non-active/non-exception statuses to
EXCEPTIONwithadmin_revoke_requestedevidence, then movesEXCEPTIONorACTIVEtoREVOKEDwith governance evidence inseat-manager/src/api/server.ts:3049andseat-manager/src/api/server.ts:3060. Those transitions match the state-machine specs inseat-manager/src/workflow/stateMachine.ts:81. - 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. - 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
REVOKEDinseat-manager/src/onboarding/store.ts:19andseat-manager/prisma/migrations/20260514165745_one_active_seat_per_operator/migration.sql:37. - Verify closure by opening the audit page filtered to
admin.seat.revoke, checking the lifecycle evidence forREVOKED, and confirming no live operations remain for the old seat. The success audit event is emitted inseat-manager/src/api/server.ts:3134.
Post-revocation review has four required checks:
Seat.statusisREVOKEDand the previous status is visible in the revoke response fields built atseat-manager/src/api/server.ts:3102.- The lifecycle evidence row contains
governance_approval_hash,governance_approval_nonce,reason_code, andactor; those keys are written inseat-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.