Spec 01 · Harness architecture

← back to index

# ByronOS Harness — core architecture (Orbit + unified owner auth + governance lens + aesthetic)

*Fable 5 spec · stop=end_turn · out_tokens=9467*

---

# ByronOS Harness — Core Architecture Spec (v1)

**Audience:** Opus 4.8, building this week against the existing ~v74 single-file PWA.
**Rule for this spec:** nothing here requires a rewrite of `layoutBudget()`, `placeChrome()`, the spring system, or the card registry. Everything mounts *around* what exists.

---

## 0. One-paragraph thesis (so 4.8 doesn't drift)

The Harness is one document with one state machine. **Orbit** is that state machine: a small registry of *spaces* whose transitions are depth dissolves. **Owner auth** is one signed token verified in one function, checked at the edge and in-page. The **Governance Lens** is an overlay that renders a single signed disclosure document — the same document the voice agent reads from, so the visual card and the spoken answer cannot diverge. The **aesthetic** is "instrument, not spectacle": graphite, one accent, critically-damped motion. Anything psychedelic is off-thesis and gets deleted.

---

## 1. Orbit Navigation Model

### 1.1 What a "space" IS

A space is **not** a page and **not** a new DOM tree. It is a registry entry + a full-viewport layer element that already exists (or is lazily created) inside the single file:

```js
// SPACES registry — lives next to the card registry in index.html
const SPACES = {
  orb:       { ring: 0, el: '#space-orb',       auth: 'public', mount: mountOrb,       legacyMode: null },
  portfolio: { ring: 2, el: '#space-portfolio', auth: 'public', mount: mountGallery,   legacyMode: 1    }, // existing gallery
  detail:    { ring: 2, el: '#space-detail',    auth: 'public', mount: mountDetail,    legacyMode: 2    },
  stack:     { ring: 2, el: '#space-stack',     auth: 'public', mount: mountStack,     legacyMode: 3    },
  signal:    { ring: 1, el: '#space-signal',    auth: 'owner',  mount: mountSignal,    legacyMode: null },
  command:   { ring: 1, el: '#space-command',   auth: 'owner',  mount: mountCommand,   legacyMode: null }, // iframe→inline later
};
```

- `ring: 0` = the orb itself (agent locus, always rendered, never unmounted).
- `ring: 1` = inner orbit — private/live (Signal, Command). Gated by `auth: 'owner'`.
- `ring: 2` = outer orbit — public portfolio surfaces. These wrap the **existing Mode 1/2/3 render paths**; `mountGallery` etc. are thin adapters that call the current mode-entry functions.

**Orbit state — the single source of truth:**

```js
const orbit = {
  current: 'portfolio',     // space id
  previous: null,
  depth: 2,                 // current ring, drives z/blur/scale
  transitioning: false,
  authLevel: 'public',      // 'public' | 'owner' — set by auth check §2
};
```

The legacy `mode` variable **stops being written by anything except a derived getter** (`get mode(){ return SPACES[orbit.current].legacyMode }`) so existing code that *reads* mode keeps working. Anything that *writes* mode gets replaced by `goToSpace()` — this is a grep-and-replace, not a rewrite. (`grep -n "mode\s*=" index.html` should return zero assignments after this ships except the getter.)

### 1.2 Dissolve-through-depth transition — exact mechanics

One function, CSS-driven, spring-timed. No per-space transition code allowed.

```js
async function goToSpace(id, { via = 'ui' } = {}) {
  const target = SPACES[id];
  if (!target) return;
  if (target.auth === 'owner' && orbit.authLevel !== 'owner') return openGate(id); // §2, fail-closed
  if (orbit.transitioning || id === orbit.current) return;
  orbit.transitioning = true;

  const from = SPACES[orbit.current], to = target;
  const inward = to.ring < from.ring; // moving toward the orb = "descend"

  // Phase A (~180ms): current space dissolves THROUGH depth
  from.el.classList.add(inward ? 'depart-outward' : 'depart-inward');
  // depart-inward: scale 1→1.06, blur 0→8px, opacity 1→0  (you fall past it)
  // depart-outward: scale 1→0.94, blur 0→8px, opacity 1→0  (it recedes behind you)

  await to.mount?.();               // idempotent; adapters no-op if already mounted
  // Phase B (~260ms, overlapping 80ms): target resolves from the opposite depth
  to.el.classList.add(inward ? 'arrive-from-depth' : 'arrive-from-front');

  orbit.previous = orbit.current; orbit.current = id; orbit.depth = to.ring;
  document.documentElement.dataset.ring = String(to.ring); // orb + chrome restyle via CSS
  history.replaceState(null, '', `#${id}`);
  setTimeout(() => { cleanupTransitionClasses(from.el, to.el); orbit.transitioning = false; }, 440);
}
```

CSS (tokens in §4): transitions use `opacity`, `transform: scale()`, `filter: blur()` only — all compositor-friendly. Timing curves come from the existing spring constants exported as `--ease-orbit: cubic-bezier(0.22, 1, 0.36, 1)` — critically damped, **no overshoot on space transitions** (overshoot stays reserved for cards).

The **orb** (ring 0) never transitions — it persists across every dissolve as the fixed point. Its treatment changes by ring via `[data-ring]` CSS: ring 1 = orb slightly larger, amber halo (you're inside the private core); ring 2 = orb compact, neutral. This is the entire "you are here" affordance. No breadcrumbs, no nav bar.

### 1.3 Voice + input touch-points

- **`voice-session.js`:** add tool `goto_space { space: enum(portfolio|detail|stack|signal|command|orb) }` → calls `goToSpace(space, {via:'voice'})`. Voice requests for owner spaces hit the same auth check — the tool returns `{denied: true, reason: 'owner_auth_required'}` and the model says so plainly. **Also fix the confirmed P1 gap in the same PR:** add `launch_card { id }` / `play_media { id }` tools mapped to the existing card-open + media-play handlers. Do not ship Orbit voice nav while "launch" still fails — that's polishing the door while the handle is broken.
- **Gestures:** MediaPipe pinch-pull toward camera = `goToSpace(inward neighbor)`; push away = outward. Wire to the same `goToSpace()`. Nothing else.
- **URL:** `#space` hash on load routes through `goToSpace()` (owner spaces fail closed to the gate).

### 1.4 Acceptance criteria — Orbit

1. Zero direct assignments to legacy `mode` remain; Mode 1/2/3 behavior unchanged when reached via `goToSpace('portfolio'|'detail'|'stack')`.
2. Any-to-any space transition completes < 500ms with no layout shift in the persistent orb.
3. `goToSpace('command')` while unauthenticated shows the gate and mounts **nothing** from the private space (no DOM, no fetch — verify in devtools Network).
4. Voice: "go to the stack", "open signal" (authed), and "launch [card]" all work.

---

## 2. Unified Owner Auth ("God mode")

### 2.1 What dies

The Command token, the Signal bearer cookie, and any harness-local secret are **deleted**, not wrapped. Blunt assessment: the current single-bearer-cookie Signal gate is theater-adjacent for a Responsible-AI lead's personal system — no expiry, no rotation story, three secrets meaning three unrotated secrets. One credential with real expiry and a kill switch is both simpler and honestly stronger.

### 2.2 Design

**One long-lived owner secret** (`OWNER_KEY`, ~32 bytes base64url, stored only in Byron's password manager; server stores only its SHA-256 hash + `AUTH_EPOCH` int). It is exchanged for **one short-lived session token**:

```
token = "v1." + base64url(payload) + "." + base64url(HMAC-SHA256(secret=SERVER_HMAC_KEY, payload))
payload = { "sub":"byron", "scope":"owner", "epoch":1, "iat":..., "exp": iat + 30d }
```

HMAC, not asymmetric — one verifier, one key, no JWT library, ~15 lines to sign and verify. `SERVER_HMAC_KEY` lives in the platform secret store (SSM/KMS-backed), never in the repo, never client-side.

**Endpoints (one small function/Lambda, shared across subdomains):**

| Endpoint | Behavior |
|---|---|
| `POST /api/auth/owner` `{key}` | constant-time compare hash(key); on success → sets cookie **and** returns `{token}` in body |
| `GET /api/auth/session` | verifies cookie or `Authorization: Bearer`; returns `{scope:'owner', exp}` or 401 |
| (env) `AUTH_EPOCH++` | kill switch — every issued token carries `epoch`; verifier rejects `payload.epoch < AUTH_EPOCH` |

**Cookie:** `os_owner=<token>; Domain=.arnao.ai; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=2592000`. One cookie covers info./sherpadude./command./mentor.arnao.ai. **Headless/CLI:** the same token string in `Authorization: Bearer` is accepted identically by `verifyOwner()` — cookie and header are two transports for one credential.

### 2.3 Where the check lives (this is the important part)

`verifyOwner(request) -> {ok, scope} | {ok:false}` exists in **exactly one module**, imported by:

1. **Edge middleware** in front of every private *endpoint*: `/api/signal/*` (MP3s + feed), `/api/command/*`, `/api/harness/*`. Default-deny: the route table lists public paths explicitly; everything else requires `ok`. This is the fail-closed boundary. Private MP3s are **never** publicly addressable — served through the verified route (or short-lived signed URLs minted by it), because client-side gating of static media is not gating.
2. **In-page**, `orbit.authLevel` is set from `GET /api/auth/session` on load. This is UX only — it decides whether to show the gate — and is explicitly *not* the security boundary. If someone flips `authLevel` in devtools, they get empty spaces and 401s.

**Auth flow, end to end:**

```
Load → GET /api/auth/session
  200 → orbit.authLevel='owner' → inner ring reachable
  401 → 'public' → goToSpace(inner) opens Gate overlay
Gate: paste OWNER_KEY → POST /api/auth/owner → cookie set → session re-check → retry goToSpace(target)
Headless: curl -H "Authorization: Bearer $TOKEN" https://command.arnao.ai/api/...
Revoke everything: bump AUTH_EPOCH. Rotate: replace OWNER_KEY hash.
```

### 2.4 Acceptance criteria — Auth

1. All three legacy secrets removed from code and configs; one grep-able `verifyOwner` implementation.
2. Direct URL to a Signal MP3 without token → 401/403. With expired/old-epoch token → 401.
3. One login on info.arnao.ai unlocks command.arnao.ai in the same browser (cookie domain check).
4. `curl` with Bearer works against Command and Signal endpoints with no browser involved.
5. Bumping `AUTH_EPOCH` invalidates a previously working token within one request.

---

## 3. Governance Lens

### 3.1 Substance vs. theater — read this first

A "signed transparency card" is board-grade **only if** (a) the disclosure is machine-verifiable against a published key, (b) the voice agent's self-description is generated *from the same document*, and (c) the signing key isn't sitting next to the app code. Anything less is a styled about-page, and a Global RAI lead shipping a styled about-page as "governance" is a self-inflicted credibility wound. Build all three or don't badge it as governance.

### 3.2 The disclosure document — single source of truth

Published at `https://info.arnao.ai/.well-known/ai-disclosure.json` + detached signature `ai-disclosure.sig` (Ed25519; public key pinned in the page **and** in a `/.well-known/ai-disclosure-pubkey.txt`). Signing happens in a build/deploy step with the private key in KMS/SSM — **never** in the runtime Lambda, never in the repo.

```json
{
  "version": "2026-02-12",
  "issuer": "arnao.ai",
  "system": "ByronOS Harness",
  "models": [
    { "role": "voice", "id": "gpt-realtime-2.1-mini", "provider": "OpenAI",
      "capabilities": ["speech", "reasoning", "tool_calling"], "voice": "coral" }
  ],
  "tools_exposed_to_model": ["open_card","show_details","filter_cards","next","prev",
    "add_to_stack","goto_space","launch_card","play_media","get_disclosure"],
  "data_handling": {
    "audio": "streamed via WebRTC to provider; not stored by this site",
    "session_transcripts": "ephemeral, discarded at session end",
    "analytics": "none | <name it truthfully>"
  },
  "auth_disclosure": "private surfaces gated by owner credential; no third-party accounts",
  "human_accountable": "Byron Arnao",
  "signature_alg": "Ed25519"
}
```

### 3.3 The Lens as overlay

- **DOM:** `#governance-lens`, a top-z overlay above all spaces (below only the gate). Not a space — it's a *lens over* whatever space is active, reinforcing "governance is orthogonal to features."
- **Invocation:** `G` key, orb long-press, voice tool `show_governance {}`.
- **Renders:** (1) the disclosure card, rendered *from the fetched JSON* — no hardcoded copy; (2) signature status: fetch doc + sig, verify with pinned pubkey via WebCrypto → `✓ Signed · verified in-browser · <version>` or a loud red `UNVERIFIED`; (3) **live session panel**: current model id, mic state, tools invoked this session (append-only array `session.toolLog` populated in the existing tool-dispatch wrapper in `voice-session.js`), and the data-handling statement.
- **"What model are you," answered safely:** add tool `get_disclosure {}` returning the parsed disclosure JSON, and one system-prompt line: *"For any question about your identity, model, capabilities, or data handling, call get_disclosure and answer strictly from its contents. Do not speculate beyond it."* Now the spoken answer, the visual card, and the well-known file are the same artifact. That's the demo line for a boardroom: *ask it what it is — it answers from a signed document you can verify yourself.*

### 3.4 Acceptance criteria — Lens

1. Disclosure JSON + sig publicly fetchable; in-browser Ed25519 verification passes; tampering one byte flips the UI to UNVERIFIED.
2. Voice "what model are you?" triggers `get_disclosure` (visible in `toolLog`) and the answer names `gpt-realtime-2.1-mini` and the data-handling stance without hedging or invention.
3. Lens opens over any space, including private ones, without breaking orbit state; `Esc` closes.
4. Private signing key demonstrably absent from repo and runtime env of the app function.

---

## 4. Aesthetic — "Instrument, not spectacle"

The look is a **flight instrument for a serious operator**: dark, matte, precise, one warm accent. Implementable entirely in CSS tokens — no image model, which is exactly why it will stop drifting.

### 4.1 Palette tokens (`:root`)

```css
:root {
  --ink-0: #0B0E12;   /* page ground — near-black, blue-leaning, matte */
  --ink-1: #12161D;   /* space background / cards at rest */
  --ink-2: #1B212B;   /* raised surfaces, lens panels */
  --line:  #2A3140;   /* hairline borders, 1px only */
  --text-1:#E8EAED;   /* primary text — warm off-white, never pure #FFF */
  --text-2:#9AA3B2;   /* secondary */
  --accent:#E5A33D;   /* orbit amber — the ONLY accent. Orb, active states, verified badge */
  --accent-dim:#8A6524;
  --danger:#D96A5B;   /* muted terracotta — UNVERIFIED, denials. Not alarm-red */
  --ring-glow: 0 0 24px rgba(229,163,61,.18); /* max permitted glow, orb only */
}
```

Hard rules: **one accent hue.** No gradients beyond a 2-stop same-hue vertical on the orb. Shadows are soft and monochrome (`rgba(0,0,0,.4)`), never colored. Inner-ring (owner) spaces may warm `--ink-1` by ~2% toward amber via `[data-ring="1"]` — felt, not seen.

### 4.2 Motion vocabulary

| Element | Behavior | Timing |
|---|---|---|
| Space dissolves | opacity + scale(±6%) + blur(8px), **no overshoot** | 180ms out / 260ms in, `--ease-orbit` |
| Cards | existing springs, overshoot allowed — cards are the only playful layer | as-is |
| Orb | slow idle breathe (scale 1↔1.015, 4s sine); ring-change morph 300ms | continuous |
| Chrome/lens | fade + 4px translateY, no scale | 160ms |
| Denied action | 2px x-shake, 120ms, once | — |

Nothing loops decoratively except the orb breathe. No parallax, no particles.

### 4.3 Typography

- **UI/prose:** one variable grotesk — **Inter** (self-hosted, weights 400/500/650). Tight tracking on headings (`-0.015em`), generous line-height on body (1.55).
- **Data/system voice:** **IBM Plex Mono** for the disclosure card, toolLog, token/session readouts, timestamps. The mono face *is* the governance register — when the system speaks about itself, it speaks in mono.
- No display faces, no third font, no gradient text, ever.

### 4.4 Explicit blacklist (delete on sight)

Neon/AI-purple (#a…f violet range), chromatic aberration, glassmorphism blur-panels, particle fields, lens flares, animated gradients, iridescence, more than one accent hue, pure-black/pure-white, drop shadows in color, any "cosmic nebula" texture. The Orbit metaphor is expressed through **depth and motion**, not through space imagery. If a surface would look at home on a crypto landing page, it's off-thesis.

### 4.5 Acceptance criteria — Aesthetic

1. All colors in the file resolve to the token set; zero hex literals outside `:root`.
2. Screenshot of each ring passes a squint test: one accent, matte ground, mono only on system-voice elements.
3. Existing card spring feel unchanged.

---

## (a) TENSION ADJUDICATED — Orbit vs. Modes

**Call: Orbit fully replaces modes as the state model, immediately — but it consumes the mode render paths as space adapters, so no render code is rewritten.** Coexistence ("shell around modes") is rejected because it creates two sources of truth for "where the user is," and every future feature — voice nav, auth gating, gesture routing, the lens — would need to consult both, which is exactly how a single-file PWA rots. The migration cost is honest and small: a derived `mode` getter for readers, `goToSpace()` for the handful of writers, and adapters that call existing mode-entry functions. That ships incrementally in one release with rollback being a single git revert. Shell-around-modes *feels* safer but converts a one-week seam into a permanent tax; replace-at-state-level pays once.

## (b) BUILD ORDER for 4.8

1. **CSS tokens + typography** (§4.1–4.3) into `:root` of index.html; migrate existing hex literals. Zero behavior change — safe first commit.
2. **`SPACES` registry + `orbit` state + `goToSpace()`** with dissolve CSS; adapters wrapping Mode 1/2/3 entry functions; derived `mode` getter; replace all `mode =` writes. Verify acceptance §1.4.1–2.
3. **Fix the P1 voice gap:** add `launch_card`/`play_media` tools in `voice-session.js`, plus `goto_space`. Verify §1.4.4.
4. **Auth backend:** `verifyOwner()` module, `POST /api/auth/owner`, `GET /api/auth/session`, HMAC token, `AUTH_EPOCH`. Deploy edge default-deny in front of Signal/Command/Harness API routes. Verify §2.4.2, 2.4.5.
5. **Auth frontend:** Gate overlay, session check on load, `orbit.authLevel` wiring into `goToSpace()`. Delete the three legacy secrets. Verify §2.4.1, 2.4.3–4.
6. **Wire `signal` and `command` as ring-1 spaces** (Command as sandboxed iframe initially; inline later — not this week).
7. **Disclosure pipeline:** author `ai-disclosure.json`; Ed25519 sign in deploy step (key in KMS); publish doc + sig + pubkey at `/.well-known/`. Verify §3.4.4.
8. **Governance Lens overlay:** render-from-JSON card, in-browser WebCrypto verification, `toolLog` capture in the tool-dispatch wrapper, live session panel. Verify §3.4.1, 3.4.3.
9. **`get_disclosure` tool + identity system-prompt line** in `voice-session.js`. Verify §3.4.2 — then record that voice exchange; it's the demo.
10. **Gestures → `goToSpace()`** (pinch-pull inward/outward). Last, because it's the least load-bearing and the easiest to cut if the week runs short.