diff --git a/Mutation-Gate-Spike-Verdict.md b/Mutation-Gate-Spike-Verdict.md new file mode 100644 index 0000000..9ec9dd1 --- /dev/null +++ b/Mutation-Gate-Spike-Verdict.md @@ -0,0 +1,50 @@ +> 🌍 [View in other languages](Languages) + + +# Mutation gate works; Task 12 per-test attribution is GO + +> **Supersedes** an earlier draft of this file that wrongly reported a "Stryker 9.6.1 +> OptionsValidator regression". **There is no regression** β€” the mutation gate runs fine. +> The earlier failure was operator error (see the gotcha below). This note corrects the +> record and captures the real spike verdict. + +## Task 12 spike verdict β€” GO (Plan A, `killedBy`) + +Ran a scoped Stryker mutation on `open-sse/utils/error.ts` (2 covering test files) on the +v3.8.29 toolchain (Stryker 9.6.1 + ajv 8.18.0, `coverageAnalysis: perTest`, tap-runner): + +- 422 mutants β€” **86 Killed**, 64 Survived, 272 NoCoverage. +- **All 86 killed mutants carry a populated `killedBy`** (86/86), and the report's + `testFiles` section resolves each `killedBy` id to a covering test file. +- So **per-test (Plan A) attribution works**: Onda 2 radiography can read `killedBy` + directly. Granularity is **per test FILE** (the tap-runner spawns one node process per + test file) β€” exactly the unit the redundancy heuristic needs ("a test file that kills no + mutant another file does not already kill"). + +The full config also instruments cleanly: `npx stryker run --dryRunOnly` reports +"Found 8 of 5632 file(s) to be mutated. Instrumented 8 source file(s) with 15488 mutant(s)" +β€” the 8-module nightly is healthy. This matches (and refreshes, on the current deps) the GO +recorded back in v3.8.27. + +## ⚠️ Gotcha that caused the false alarm: `stryker -c` is `--concurrency`, NOT `--config-file` + +`npx stryker run -c stryker.conf.json` sets `concurrency = "stryker.conf.json"` (a string), +which fails schema validation with a **mis-attributed** error: +`Config option "concurrency" must match pattern "^(100|[1-9]?[0-9])%$"`. Stryker still loads +the real config via auto-discovery, so the run _looks_ like the config is broken when it is +not. `jsPropertySyntax` (an ajv-6 option, a no-op under ajv 8) makes the validator's error +path unhelpful, but the data is the giveaway: `options.concurrency === "stryker.conf.json"`. + +**Correct invocations:** + +- Default config (`stryker.conf.json` in cwd): `npx stryker run` (no flag). +- Explicit config file: pass it as the **positional** arg β€” `npx stryker run my.conf.json`. +- `-c` / `--concurrency` takes a **number or percentage** (`-c 4`, `-c 50%`). + +## Standing Onda-2 blocker (unchanged): budget, not tooling + +The real blocker for a complete nightly remains **runtime budget** β€” the 8-module run is +15488 mutants and the god-files (`chatCore`, `combo`) dominate ~β…”; the conc=4 nightly timed +out at 180min (run `27705123780`). Levers: exclude god-files until the Onda-3 splits, seed +the incremental file, batch per night, or accept partial. The per-test spike does not change +that β€” it only confirms the attribution mechanism Onda 2 will rely on. diff --git a/Remote-Mode.md b/Remote-Mode.md new file mode 100644 index 0000000..46a610f --- /dev/null +++ b/Remote-Mode.md @@ -0,0 +1,170 @@ +> 🌍 [View in other languages](Languages) + + +# Remote Mode + +Run the `omniroute` CLI on your laptop while OmniRoute itself runs somewhere else +(a VPS, a home server, another machine on your Tailnet). You log in once with +`omniroute connect`, and from then on **every** CLI command targets that remote +server β€” same commands, same output, just executed against the remote. + +There is no second tool to install: remote mode is the regular `omniroute` CLI +plus scoped **access tokens**. + +```bash +npm install -g omniroute # the normal CLI +omniroute connect 192.168.0.15 # log in (password β†’ scoped token) +omniroute models list # ← now lists the REMOTE server's models +omniroute configure codex # ← writes a local Codex profile from the remote catalog +``` + +--- + +## How it works + +``` +your laptop remote OmniRoute (VPS) +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ omniroute CLI β”‚ POST /api/cli/connect (password β†’ token) β”‚ +β”‚ context: vps β”‚ ───────────────► β”‚ mints a scoped access token β”‚ +β”‚ baseUrl, token β”‚ Authorization: Bearer oma_live_… β”‚ +β”‚ β”‚ ───────────────► β”‚ every management route, scope- β”‚ +β”‚ writes configs β”‚ ◄─────────────── β”‚ checked per the token's scope β”‚ +β”‚ LOCALLY β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +- **Contexts** store one server each (`~/.omniroute/config.json`, `chmod 600`). + `omniroute contexts use ` switches the active server; `default` is local. +- **Access tokens** (`oma_live_…`) authorize management commands. They are + distinct from inference API keys (`sk-…`, used for `/v1/chat/completions`). +- Only the SHA-256 hash of a token is stored server-side. The plaintext is shown + **once**, at creation. + +--- + +## Connecting + +### With the management password (bootstrap) + +```bash +omniroute connect 192.168.0.15 +# Management password for http://192.168.0.15:20128: ******** +# βœ” Connected to http://192.168.0.15:20128 β€” context '192.168.0.15' (scope: admin) +``` + +The password flow mints an **admin** token by default (you hold the password, so +you already have full control). Downscope with `--scope`: + +```bash +omniroute connect 192.168.0.15 --scope write +``` + +Options: `--port

` (when the host has none), `--name ` (context name), +`--scope read|write|admin`. A full URL is honoured as-is: +`omniroute connect https://omni.example.com`. + +### With a pre-generated token + +Generate a scoped token in the dashboard (or with `omniroute tokens create`) and +paste it β€” no password needed: + +```bash +omniroute connect 192.168.0.15 --key oma_live_xxxxxxxx +``` + +The CLI validates it via `GET /api/cli/whoami` and saves it as the active context. + +--- + +## Scopes + +Three levels, hierarchical (`admin βŠƒ write βŠƒ read`): + +| Scope | Can do | +|-------|--------| +| `read` | list/inspect β€” `models list`, `providers status`, `logs`, `usage`, `cost` | +| `write` | read **+** configure/apply β€” `setup-codex`, `keys add`, `config set`, combos | +| `admin` | write **+** manage β€” `tokens` CRUD, add providers, services, policy, oauth | + +The server infers the scope each route requires from the HTTP method +(`GET`β†’read, mutationsβ†’write) plus an admin allowlist for sensitive surfaces +(`/api/cli/tokens`, `/api/providers` mutations, `/api/oauth`, `/api/services`, …). +A token with insufficient scope gets `403` with a clear message. + +> Routes that spawn processes (`/api/services/*`, `/api/mcp/*`, …) stay +> **loopback-only** β€” a remote token can never reach them, regardless of scope. + +--- + +## Managing tokens + +```bash +omniroute tokens create --name "laptop" --scope write [--expires 30] +# ↳ prints the secret ONCE β€” copy it now +omniroute tokens list # masked: id, name, scope, prefix, status, expiry +omniroute tokens revoke # revoke immediately +omniroute tokens scopes # explain the three scopes +``` + +`tokens` commands require an **admin** credential. You can also manage tokens in +the dashboard under **Settings β†’ Access Tokens** (create, revoke, copy-once). + +--- + +## Configuring a coding CLI from the remote catalog + +`omniroute configure` reads the **active server's** live model catalog and writes +a config on **your** machine. + +```bash +omniroute configure codex +# Providers: glm, kmc, ollamacloud, opencode-go, … +# Provider: glm +# Model id: glm/glm-5.2 +# βœ” Wrote ~/.codex/glm52.config.toml +# Use it: codex --profile glm52 + +# non-interactive +omniroute configure codex --provider glm --model glm/glm-5.2 --name glm52 +``` + +The written profile references the inference key by env var +(`OMNIROUTE_API_KEY`) β€” the secret is never written to disk. For the one-time +base Codex setup (the `[model_providers.omniroute]` block), see +[CODEX-CLI-CONFIGURATION.md](./CODEX-CLI-CONFIGURATION.md). + +--- + +## Switching back to local + +```bash +omniroute contexts use default # back to localhost +omniroute context current # show active server, auth, scope +omniroute contexts list # all contexts +``` + +--- + +## Security notes + +- Token plaintext is shown once; only the SHA-256 hash is persisted (same as API keys). +- `omniroute connect` reuses the login brute-force lockout + audit logging. +- Prefer HTTPS or a Tailnet for the transport; a bare host defaults to `http://` + for LAN/Tailscale convenience β€” pass a full `https://…` URL for TLS. +- The local context file is `~/.omniroute/config.json` (`chmod 600`); tokens are + never printed in logs (masked to a prefix). + +--- + +## API endpoints (reference) + +| Method | Route | Auth | Scope | +|--------|-------|------|-------| +| POST | `/api/cli/connect` | management password | β€” (public, password-gated) | +| GET | `/api/cli/whoami` | access token | read | +| GET | `/api/cli/tokens` | access token | admin | +| POST | `/api/cli/tokens` | access token | admin | +| DELETE | `/api/cli/tokens/:id` | access token | admin | + +See [openapi.yaml](../reference/openapi.yaml) for full schemas.