# ORACLE OS — Role-Separation Goal (Track F)

**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.

---

## 1. The concept (what we are building)

Serve **two audiences** from the one dashboard, with role decided **server-side** and
enforced on **both** sides:

- **OWNER / ADMIN** (Ryan = the `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.
- **MEMBER / VIEWER** (a non-admin logged-in user): a **PUBLIC PORTAL** view — the safe,
  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.

---

## 2. Current state of the role system (what EXISTS vs what's MISSING)

Audited against `command-center-server.mjs` and `oracle.html` on 2026-07-01.

### 2.1 Server — the identity/role foundation is ALREADY BUILT and solid

- **Roles + ranks** (`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`).
- **Multi-user registry** (`state/cc_users.json`): tokens stored only as `sha256(salt+token)`
  + last-4; plaintext shown once on mint. Owner is **seeded** from `CC_CONTROL_TOKEN`
  (`seedOwner()` ~L502) as `usr_owner` so the legacy owner login is unchanged.
- **Role resolution** (`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.
- **Authz gates** (`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 the
  authenticated session — so the client CAN reliably learn its role.
- **Control endpoints ALREADY server-gated (admin+ via `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.

### 2.2 Client — the gating hooks EXIST but the master switch is NEVER FLIPPED

- The CSS rule exists (`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).
- **MISSING — the core bug:** *nothing ever adds `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.

### 2.3 Server — the read-side leaks that are NOT yet role-filtered

- **`/api/system-stats`** (~L3952): returns `buildSystemStats()` in FULL to any authenticated
  user — 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.**
- **Admin-channel CONTENT** (`/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.)

### 2.4 Client — admin surfaces lacking `data-needs`

- The **Test Requests** accordion (`acc-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).
- The Home hardware/provider/connection panels are built in JS (`renderHome()` sub-updaters,
  ~L4620+) rather than static tagged containers, so hiding them cleanly is a Phase 2 layout job.

---

## 3. Requirement (the contract)

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.

---

## 4. Phased plan

### Phase 1 — make the existing owner-vs-member gating correct & complete (client + server) — SAFE, FOUNDATIONAL
The part that clearly cannot lock out the owner. **This is the phase implemented now.**

- **Client (`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`).
- **Server (`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.
- **Safety:** all role decisions flow through `currentUser()` → owner always resolves to
  owner/admin → full data + no 403. Restrictions are purely additive for non-admins.

### Phase 1.5 — close the remaining server read-leaks (needs light live verification)
- Re-check admin/secret **channel content** in `/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.)

### Phase 2 — the member public-portal LAYOUT
- Give members a purpose-built Home instead of an admin Home with holes: hide/replace the
  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.

### Phase 3 — per-role features & polish
- Owner-only user-management UX polish, admin (Taz) vs owner distinctions, viewer (read-only,
  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`.

---

## 5. Apply / test

- `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).
- Verify member vs owner: log in as owner (full stats, all controls); mint a `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.
- Regression: owner must retain FULL access; login, voice/video, KAIVERSE, layout unaffected.

---

*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.*
