Doc type: Single source-of-truth plan for splitting the Oracle OS Command Center into
an owner/admin Command Center and a member/viewer public portal, served from the
same oracle.html + command-center-server.mjs on :3001.
Owner: Ryan (nastermodx) — tester/verifier.
Reconstructed: 2026-07-01 from the live code (the original detailed plan was overwritten
and is not in git; only the Track F summary in ORACLE-OS-COMMAND-CENTER-GOAL.md §5 survived).
This doc replaces that partial stub and is grounded in the ACTUAL auth/role system in the
code, not memory.
---
Serve two audiences from the one dashboard, with role decided server-side and
enforced on both sides:
CC_CONTROL_TOKEN holder / seeded role:'owner'; plus any role:'admin' minted user such as Taz): the FULL Command Center — every control,
fleet restart / sleep / wake, provider & connection internals, hardware/CPU/RAM/GPU/disk
telemetry, raw logs, engine tests, sensitive channels, the admin panel, user minting —
everything that exists today.
read-mostly subset (public channels + chat, agent status/leaderboard, the full KAIVERSE,
and any sanitized public surface), with all admin/dangerous controls and sensitive
data HIDDEN client-side AND withheld server-side. A member must not be able to see
hardware stats or trigger control actions even by calling the APIs directly.
Security principle: CSS/JS hiding is UX, not security. Every gate is enforced twice —
declaratively in the client (data-needs="admin" + body.scope-member) *and* authoritatively
in the server (role-checked endpoints + role-filtered response bodies). The client is never
trusted to assert its own role; the server resolves role from the session/token.
---
Audited against command-center-server.mjs and oracle.html on 2026-07-01.
command-center-server.mjs ~L463): ROLES = ['owner','admin','member','viewer'], ROLE_RANK, and ROLE_DEFAULT_PERMS (owner adds user_management, admin adds control+discord,
member gets read+own_data, viewer gets read).
state/cc_users.json): tokens stored only as sha256(salt+token)CC_CONTROL_TOKEN (seedOwner() ~L502) as usr_owner so the legacy owner login is unchanged.
currentUser(req,q) ~L567): session cookie → userId → record, OR x-cc-token/?token= → resolveUserByToken(), OR the legacy owner token → usr_owner.
When auth is disabled (no CC_CONTROL_TOKEN) it returns the owner so nothing breaks.
requireRole/requireControl/requireOwner ~L595): write a 401/403 and return null when the caller is missing/under-ranked. requireControl = admin+,
requireOwner = owner.
/api/me (~L3416): reports role, isOwner, isAdmin, canManageUsers for theauthenticated session — so the client CAN reliably learn its role.
requireControl): /api/control/restart, /api/control/ai/<name> (sleep/wake/restart),
/api/ai/<name>/config, channel-settings write, /api/training-send,
/api/channel-send, POST /api/owner-profile. User management (/api/users*) is
owner-only via requireOwner.
/api/channels (~L3816) already resolves role: returns isOwner = roleHas(me.role,'admin')so only owner/admin unlock the admin/secret channel IDs; member/viewer get read scope.
oracle.html L514): body.scope-member [data-needs="admin"]{display:none !important}.data-needs="admin" is present on the Server Control accordion + its restart buttons (L2122–2129) and the owner-only buttons in renderAdmin() (L4318–4319).
loadMe() (~L6125) fetches /api/me into me; isAdminRole() / isOwnerRole() helpers(~L2505) exist and already gate a few inline controls (AI config alert L5312, roster
Controls button L5691, user-mgmt render L4334).
body.scope-member.* scope-member appears ONLY in the CSS rule (L514) and there is no classList.add('scope-member') anywhere. So
the entire data-needs="admin" mechanism is dormant — a logged-in member currently
sees the admin controls in the DOM. The switch is built but never thrown.
/api/system-stats (~L3952): returns buildSystemStats() in FULL to any authenticateduser — CPU model/percent/per-core, RAM used/total, GPU, disk, host name/platform/release,
uptime, engine process RSS, provider internals + cooldowns. This is exactly the
hardware/CPU/RAM leakage Track F forbids for members. No role filter.
/api/logs (~L3304): raw log tails (ecosystem/sentinel/startup/overnight) — ops-internal,readable by any authenticated user. No role gate.
/api/tests (~L3311): engine test outputs + Kai-Coder sandbox file snippets — internal,readable by any authenticated user. No role gate.
/api/transcripts, /api/thread-messages): the channel *list*is scoped, but message-content endpoints do not re-check whether the requested channel is an
admin/secret channel. A member could request an admin channel's transcript directly. (Deeper
fix — flagged for Phase 1.5/2, see §4.)
data-needsacc-tests, L2134) and Logs / Issues accordion (acc-logs, L2150) have no data-needs="admin" — members would see them (and they poll the
leaky endpoints above).
renderHome() sub-updaters,~L4620+) rather than static tagged containers, so hiding them cleanly is a Phase 2 layout job.
---
1. Role is decided server-side from session/token and exposed via /api/me. Client mirrors
it but never asserts it.
2. Client: for any non-admin session, body.scope-member is set so every data-needs="admin"
element is hidden; default to the hidden (least-privilege) state until /api/me resolves so a
slow load never flashes controls.
3. Every admin-only control/panel carries data-needs="admin" (or an equivalent role check):
fleet/server restart, per-AI sleep/wake/restart + config, Server Control, Test Requests, Logs,
provider/connection internals, hardware telemetry, RF/camera admin bits, voice-call admin
actions, user management.
4. Server: every sensitive action endpoint rejects non-admin sessions with 403
(already true for the control endpoints; extend to any that trust the client). Every sensitive
data endpoint returns a role-filtered body — members get a sanitized subset, never
hardware/provider internals or raw logs/tests.
5. Never weaken the owner. The CC_CONTROL_TOKEN holder / usr_owner / any admin keeps
FULL access. Restrictions are ADD-ONLY for member/viewer. No change may lock the owner out.
---
The part that clearly cannot lock out the owner. This is the phase implemented now.
oracle.html): 1. Throw the master switch: default <body class="scope-member"> (least-privilege) and, in
loadMe(), remove scope-member only when isAdminRole() is true; re-add it otherwise.
Owner/admin never carry the class → never hidden. This activates every existing
data-needs="admin" rule.
2. Add data-needs="admin" to the ungated admin accordions: Test Requests (acc-tests)
and Logs / Issues (acc-logs).
command-center-server.mjs): 3. Role-filter /api/system-stats: admin+ gets the full body; member/viewer get a sanitized
subset (agent counts + minimal bot roster: name/online/role/color) with no cpu/ram/gpu/
disk/host/uptime/engine/procRss/providers/cooldowns.
4. Gate /api/logs and /api/tests with requireControl (admin+) → members get 403; the
now-hidden client panels never call them anyway.
currentUser() → owner always resolves toowner/admin → full data + no 403. Restrictions are purely additive for non-admins.
/api/transcripts + /api/thread-messages: if the requested channel ∈ ADMIN_CHANNEL_IDS and the caller is not admin+, return an empty/403
scoped result. (Deferred out of Phase 1 because it touches the transcript read path and wants a
live test to confirm public channels still load for members.)
hardware + provider/connection cards in renderHome(), relabel the "Admin" identity accordion
as a plain profile card, and surface the member-appropriate set (public channels + chat, agent
leaderboard/status, full KAIVERSE, any sanitized public vitals). Purely additive, role-branched
rendering.
no chat compose) vs member (chat allowed) differences, lock/resume UX, and data-center-grade
hardening. Optional Discord-OAuth verification of the currently owner-asserted discordId.
---
oracle.html changes → hard-refresh (Ctrl+Shift+R), no restart.command-center-server.mjs changes → .\Start-Dashboard.ps1 (restarts :3001 only;leaves the engine, pipeline, and bots alone).
member token,log in as that user in a second browser → confirm no hardware stats, no control buttons,
/api/system-stats sanitized, /api/logs & /api/tests → 403, control POSTs → 403.
---
*Grounding: the live command-center-server.mjs (roles/registry L441–608, endpoint gates
L3369–4332) and oracle.html (scope CSS L514, data-needs L2122+, loadMe L6125) as of 2026-07-01,
plus ORACLE-OS-COMMAND-CENTER-GOAL.md §5 Track F.*