diff --git a/.claude/rules/audio-speech.md b/.claude/rules/audio-speech.md deleted file mode 100644 index 55b59fc..0000000 --- a/.claude/rules/audio-speech.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -paths: - - "image/app/audio_proxy.py" - - "image/app/speech_models.py" - - "image/app/deep_health.py" - - "image/parakeet_patches/**" - - "scripts/test-audio-with-speakers.sh" - - "docs/AUDIO_API.md" ---- - -# Audio / speech stack (Parakeet STT + Sortformer diarizer + Kokoro TTS on Spark 2) - -## Changing the parakeet-asr container - -- `image/parakeet_patches/` (`main.py`, `diarizer.py`) is an overlay copied into the `parakeet-asr` container by the "Reapply speech-model patches" dashboard action (`image/app/speech_models.py`). This is the **only** durable way to change that container — `docker exec` / pip changes inside it die on `docker rm`. -- **Never install `cuda-python` in parakeet-asr** to "fix" the startup warning about CUDA graphs being disabled. The warning is harmless; enabling the graph path crashes real decode with illegal memory access on this GPU/CUDA-13 stack (GB10/sm_121). The slow path served 11k+ requests with zero failures — leave it alone. -- Pin/constrain torch versions when pip-installing anything into NGC-based containers on the Sparks (ABI breaks otherwise); expect ARM64 wheel gaps and source builds (`--no-build-isolation` for torchaudio). Applies to `spark_embed` too. - -## Testing audio endpoints - -- Test with **real speech** (e.g. `say -o /tmp/t.wav --data-format=LEI16@16000 ""`), not tones/silence — zero-token audio skips the decoder paths where crashes live. -- Send audio requests to Spark 2 **sequentially** in tests/scripts. Parallel audio requests can race (cuFFT → 503), and the single GPU serializes them anyway. -- End-to-end suite (hits the LIVE cluster): - -```bash -./scripts/test-audio-with-speakers.sh # from repo root -``` - -`SPARK_CONTROL` defaults to `http://127.0.0.1:9999` (a running local dev server); point it at the installed package URL otherwise. - -## API quirk - -Spark Control's `/v1/models` lists *audio* models (STT model + Kokoro voices) by design — **not** the loaded LLM. Discover the LLM via `/api/status` (`vllm.current_model`). - -Diarizer caps at 4 speakers (Sortformer `diar_sortformer_4spk-v1`). diff --git a/.claude/rules/audio-speech.md b/.claude/rules/audio-speech.md new file mode 120000 index 0000000..a585e7d --- /dev/null +++ b/.claude/rules/audio-speech.md @@ -0,0 +1 @@ +../../docs/guides/audio-speech.md \ No newline at end of file diff --git a/.claude/rules/fastapi-image.md b/.claude/rules/fastapi-image.md deleted file mode 100644 index 4eef500..0000000 --- a/.claude/rules/fastapi-image.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -paths: - - "image/**" ---- - -# FastAPI image (`image/`) - -Standalone FastAPI app (Python ≥3.11; ships on `python:3.12-slim`; UI on port 9999; vanilla HTML/CSS/JS, no framework). Python has no configured linter/formatter — match the style of the file you're editing. - -## Local dev (no StartOS) - -```bash -cd image -python3 -m venv .venv && source .venv/bin/activate # one-time -pip install -e . -export SPARK1_HOST= SPARK1_USER= SPARK2_HOST= SPARK2_USER= SSH_KEY_PATH= -# Required outside the container — these default to paths under /data, which only exists in the image -# (missing REDACTION_MAP_DB crashes startup; missing CONNECTIVITY_LOG 500s /api/status): -export REDACTION_MAP_DB=/tmp/redaction_maps.db CONNECTIVITY_LOG=/tmp/connectivity.json -uvicorn app.server:app --host 0.0.0.0 --port 9999 --reload -``` - -Other env vars: `BIND_PORT`, `MODELS_YAML`, `SSH_DIR`, `SSH_KNOWN_HOSTS`, `MODELS_OVERRIDES`, `SERVICES_OVERRIDES`. - -## Tests - -No pytest harness — each suite is a standalone script run with the `image/.venv` interpreter (system python3 has no deps). See the redaction and audio rules for the suites themselves. - -## Conventions - -- Pydantic request models go at **module scope**, never inside a `build_router()` body (FastAPI silently 422s otherwise). -- New external-facing endpoints get documented in `docs/` (`AUDIO_API.md`, `EMBEDDINGS.md`, `REDACTION_GATEWAY.md`) and noted in release notes. - -## Layout - -- `image/app/server.py` — FastAPI entry; routers live in sibling modules (`audio_proxy.py`, `llm_proxy.py`, `embeddings_proxy.py`, `redaction_gateway.py`, `swap.py`, `health.py`, `deep_health.py`, `connectivity.py`, …). -- `image/app/static/` — the dashboard UI. -- `image/models.yaml` — vLLM model catalog bundled into the image. -- `image/spark_embed/` — Dockerfile + app for the embeddings container; built ON a Spark (ARM64, NGC PyTorch base — see the audio/cluster rule for NGC torch-pinning caveats). diff --git a/.claude/rules/fastapi-image.md b/.claude/rules/fastapi-image.md new file mode 120000 index 0000000..57075d8 --- /dev/null +++ b/.claude/rules/fastapi-image.md @@ -0,0 +1 @@ +../../docs/guides/fastapi-image.md \ No newline at end of file diff --git a/.claude/rules/redaction.md b/.claude/rules/redaction.md deleted file mode 100644 index 028085c..0000000 --- a/.claude/rules/redaction.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -paths: - - "image/app/redaction/**" - - "image/app/redaction_gateway.py" - - "docs/REDACTION_GATEWAY.md" ---- - -# Redaction (`/scrub` + `/rehydrate`) - -- `image/app/redaction/scrub.py` + `test_scrub_leak.py` are vendored **byte-for-byte** from the CRM repo (sha recorded in `redaction/__init__.py`). **Never edit them here** — change them in the CRM repo, re-vendor (`cp`), update the sha, re-run the leak test. -- The gateway around the vendored scrubber is `image/app/redaction_gateway.py`. Its token-map store lives on `/data` (`REDACTION_MAP_DB`, default `/data/redaction_maps.db`) and fails closed if it can't open — set the env var when running outside the container. - -## Test suites — both must pass before shipping ANY redaction change - -```bash -cd image -.venv/bin/python -m app.redaction.test_gateway # /scrub + /rehydrate acceptance; offline, no cluster needed -.venv/bin/python app/redaction/test_scrub_leak.py # vendored golden-file leak test; offline -``` - -Keep the leak test green against the vendored `scrub.py` after any re-vendor. - -Policy context: scrubbed text via `/scrub` is the **only** sanctioned path toward frontier/cloud models — see the whole-repo privacy rule in CLAUDE.md. diff --git a/.claude/rules/redaction.md b/.claude/rules/redaction.md new file mode 120000 index 0000000..2a560b8 --- /dev/null +++ b/.claude/rules/redaction.md @@ -0,0 +1 @@ +../../docs/guides/redaction.md \ No newline at end of file diff --git a/.claude/rules/startos-package.md b/.claude/rules/startos-package.md deleted file mode 100644 index bccc6e2..0000000 --- a/.claude/rules/startos-package.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -paths: - - "package/**" ---- - -# StartOS package (`package/`) - -TypeScript wrapper that ships the Docker image as an s9pk. `@start9labs/start-sdk` pinned `1.3.3`, Node ≥22, bundled by `@vercel/ncc`. - -## Commands - -```bash -cd package -npm i # one-time -make x86 # typecheck + ncc bundle + docker build + pack → spark-control_x86_64.s9pk -make install # sideload to the Start9 server; needs "host: http(s)://.local" in ~/.startos/config.yaml -npm run check # tsc --noEmit — run after any startos/ edit; make x86 also runs it -npm run prettier # prettier --write startos (no semicolons, single quotes, trailing commas) -``` - -`make aarch64` for ARM Start9 servers. `make install` picks the newest `*.s9pk` in `package/` and restarts the live spark-control service — get a go/no-go first. - -## Versioning & release notes - -- Version format is `X.Y.Z:N` (`:N` = revision). Bump in `package/startos/versions/v0_1_0.ts`; **replace** the release notes — never leave old notes behind under an extra key (any unknown key fails `tsc`). -- New external-facing endpoints get noted in release notes for downstream app developers (Recap Relay, Ten31 Transcripts, CRM, Signal Engine consume these APIs). - -## Layout - -- `package/startos/` — manifest, interfaces, actions (`configureSparks`, `showPublicKey`), `versions/v0_1_0.ts` (current version string + release notes). -- The "Reapply speech-model patches" action is **not** a StartOS action — it's a dashboard action implemented in `image/app/speech_models.py`. diff --git a/.claude/rules/startos-package.md b/.claude/rules/startos-package.md new file mode 120000 index 0000000..29c3524 --- /dev/null +++ b/.claude/rules/startos-package.md @@ -0,0 +1 @@ +../../docs/guides/startos-package.md \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..bc155a5 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,59 @@ +# AGENTS.md + +This file provides guidance to coding agents (Claude Code and others) when working with code in this repository. (Claude Code reads it via the `CLAUDE.md` symlink.) + +Browser-based StartOS 0.4 package controlling a dual NVIDIA DGX Spark AI cluster: one-click vLLM model swaps, plus health, proxying, and APIs for speech (STT/diarization/TTS), embeddings, and redaction. + +Subsystem guidance lives in `docs/guides/` and loads when matching files are touched (Claude Code lazy-loads via `.claude/rules/` symlinks; other agents read the guides directly): `startos-package.md` (build/versioning, `package/**`), `fastapi-image.md` (dev server/env/layout, `image/**`), `redaction.md` (vendoring + test gates), `audio-speech.md` (parakeet patches, cluster-container footguns, audio testing). **Read `docs/guides/audio-speech.md` before touching the Sparks' containers over SSH** — ops sessions don't trip the path scoping. + +## Stack + +- Two halves, always coordinated: + - `image/` — standalone FastAPI app (Python ≥3.11; UI on port 9999; vanilla HTML/CSS/JS). + - `package/` — StartOS 0.4 wrapper (TypeScript) that ships the Docker image as an s9pk. +- Build host needs `start-cli`, Node ≥22 + npm, and Docker. +- Cluster runtimes live **on the Sparks, not in this repo** (`spark-vllm-docker`, the parakeet/kokoro/embeddings containers). This repo is the controller; it reaches them over SSH + HTTP. +- Sparks are ARM64 (GB10 Grace-Blackwell, sm_121, CUDA 13). Services: vLLM `:8888` (Spark 1); `parakeet-asr` `:8000`, Kokoro TTS `:8880`, bge-m3 embeddings + Qdrant (Spark 2). See `docs/` for API contracts. + +## Commands (headlines — details in the scoped rules) + +```bash +(cd package && make x86) # build the s9pk; make install sideloads (restarts live service — ask first) +(cd image && uvicorn app.server:app --port 9999) # local dev — needs env vars, see fastapi-image rule +(cd image && .venv/bin/python -m app.redaction.test_gateway) # offline redaction suite 1 +(cd image && .venv/bin/python app/redaction/test_scrub_leak.py) # offline redaction suite 2 +./scripts/test-audio-with-speakers.sh # e2e audio — hits the LIVE cluster +``` + +## Layout + +- `image/app/` — FastAPI app (`server.py` entry, routers in sibling modules, `static/` dashboard UI). +- `package/startos/` — StartOS manifest, interfaces, actions, version + release notes. +- `docs/` — `AUDIO_API.md`, `EMBEDDINGS.md`, `REDACTION_GATEWAY.md` (consumer-facing API refs; update with API changes). +- `README.md` (overview), `HANDOFF.md` (fresh-user install guide), `runbook.md` (ops notes), `known-issues.md`, `ROADMAP.md` (longer-term backlog — items move into "Current state" below when picked up). + +## Conventions + +- Every shipped change = version bump + release notes + rebuilt s9pk (version format `X.Y.Z:N`; details in the startos-package rule). +- Commit messages: `vX.Y.Z:N - short lowercase summary`. **Never add a Co-Authored-By / Claude attribution trailer.** +- The package owner is non-technical: explain infra effects in plain English and get an explicit go/no-go before mutating the cluster. +- New external-facing endpoints get documented in `docs/` and noted in release notes for downstream app developers (Recap Relay, Ten31 Transcripts, CRM, Signal Engine consume these APIs). + +## Always / Never (cluster-wide) + +- **Always** confirm with the user before swap/stop/restart of anything on the live cluster. Read-only probes and dry-runs are fine without asking. +- **Always** use the Spark's **IP** for HTTP probes — `.local` mDNS names can resolve IPv6-first and hang httpx (vLLM and friends bind IPv4 only). Never trust `.local` hostnames inside HTTP client code. +- **Always** pass `SSH_KEY_PATH` / `-i ` explicitly in scripted SSH; non-interactive shells have no ssh-agent identities. +- **Never** route audio or transcripts to cloud services — speech stays on the LAN. (Scrubbed text via `/scrub` is the only sanctioned path toward frontier models.) +- **Never** commit owner-specific hostnames, IPs, usernames, or names into package strings, UI text, or docs — this package gets shared; use placeholders (`` style). +- **Never** install `cuda-python` in `parakeet-asr` — crashes real decode on this GPU/CUDA-13 stack; full story in the audio-speech rule. + +## Current state + +- **Working (v0.18.0:0, installed and serving):** swap dashboard; chat / transcribe / diarize(+chunk) / TTS proxies; embeddings + rerank + hybrid search (Qdrant); `/scrub` + `/rehydrate`; label-merge incl. dual-channel mode. Spark 2 audio stack is healthy (11k+ requests/12h, all 200). +- **In progress — Signal Engine "flakiness":** diagnosed, not a server bug — transient 1–4s unresponsiveness while the single GPU is continuously busy. Remedy is client-side; a drafted message (in-flight cap 2, hard ceiling 3 global across audio endpoints, retry-with-backoff on timeout/503) is with the owner to forward to that dev. +- **Decided, not implemented:** remote access stays WireGuard/Tailscale split-tunnel — no public interface, so no API auth built; an empirical concurrency sweep is offered but needs the owner's explicit OK in a quiet window. +- **Known limits:** `/health` blips while the GPU is busy (mitigated client-side); dual-channel can miss a quiet local word under loud remote bleed; the connectivity log misses sub-5s outages between 5s polls; diarizer caps at 4 speakers. +- **Repo wart:** commit `367d986` is labeled `v0.13.0:4` but actually contains everything through v0.18.0:0 — per-version commits for v0.14–v0.18 are missing. Keep commit messages accurate going forward. +- **Hosting:** repo pushes to the owner's self-hosted Gitea — remote `gitea`, branch `master`, over SSH (host alias + key live in the local `~/.ssh/config`; no owner-specific details belong in the repo). Push there after committing. +- **Next:** (1) owner forwards the concurrency note to the Signal Engine dev; (2) run the concurrency sweep if the dev wants the measured knee; (3) add the `--memory` cap to parakeet-asr via the Reapply-patches action; (4) pick the next item from ROADMAP.md. diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 4918c6d..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,59 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -Browser-based StartOS 0.4 package controlling a dual NVIDIA DGX Spark AI cluster: one-click vLLM model swaps, plus health, proxying, and APIs for speech (STT/diarization/TTS), embeddings, and redaction. - -Subsystem guidance lives in `.claude/rules/` and loads when matching files are touched: `startos-package.md` (build/versioning, `package/**`), `fastapi-image.md` (dev server/env/layout, `image/**`), `redaction.md` (vendoring + test gates), `audio-speech.md` (parakeet patches, cluster-container footguns, audio testing). **Read `audio-speech.md` before touching the Sparks' containers over SSH** — ops sessions don't trip the path scoping. - -## Stack - -- Two halves, always coordinated: - - `image/` — standalone FastAPI app (Python ≥3.11; UI on port 9999; vanilla HTML/CSS/JS). - - `package/` — StartOS 0.4 wrapper (TypeScript) that ships the Docker image as an s9pk. -- Build host needs `start-cli`, Node ≥22 + npm, and Docker. -- Cluster runtimes live **on the Sparks, not in this repo** (`spark-vllm-docker`, the parakeet/kokoro/embeddings containers). This repo is the controller; it reaches them over SSH + HTTP. -- Sparks are ARM64 (GB10 Grace-Blackwell, sm_121, CUDA 13). Services: vLLM `:8888` (Spark 1); `parakeet-asr` `:8000`, Kokoro TTS `:8880`, bge-m3 embeddings + Qdrant (Spark 2). See `docs/` for API contracts. - -## Commands (headlines — details in the scoped rules) - -```bash -(cd package && make x86) # build the s9pk; make install sideloads (restarts live service — ask first) -(cd image && uvicorn app.server:app --port 9999) # local dev — needs env vars, see fastapi-image rule -(cd image && .venv/bin/python -m app.redaction.test_gateway) # offline redaction suite 1 -(cd image && .venv/bin/python app/redaction/test_scrub_leak.py) # offline redaction suite 2 -./scripts/test-audio-with-speakers.sh # e2e audio — hits the LIVE cluster -``` - -## Layout - -- `image/app/` — FastAPI app (`server.py` entry, routers in sibling modules, `static/` dashboard UI). -- `package/startos/` — StartOS manifest, interfaces, actions, version + release notes. -- `docs/` — `AUDIO_API.md`, `EMBEDDINGS.md`, `REDACTION_GATEWAY.md` (consumer-facing API refs; update with API changes). -- `README.md` (overview), `HANDOFF.md` (fresh-user install guide), `runbook.md` (ops notes), `known-issues.md`, `ROADMAP.md` (longer-term backlog — items move into "Current state" below when picked up). - -## Conventions - -- Every shipped change = version bump + release notes + rebuilt s9pk (version format `X.Y.Z:N`; details in the startos-package rule). -- Commit messages: `vX.Y.Z:N - short lowercase summary`. **Never add a Co-Authored-By / Claude attribution trailer.** -- The package owner is non-technical: explain infra effects in plain English and get an explicit go/no-go before mutating the cluster. -- New external-facing endpoints get documented in `docs/` and noted in release notes for downstream app developers (Recap Relay, Ten31 Transcripts, CRM, Signal Engine consume these APIs). - -## Always / Never (cluster-wide) - -- **Always** confirm with the user before swap/stop/restart of anything on the live cluster. Read-only probes and dry-runs are fine without asking. -- **Always** use the Spark's **IP** for HTTP probes — `.local` mDNS names can resolve IPv6-first and hang httpx (vLLM and friends bind IPv4 only). Never trust `.local` hostnames inside HTTP client code. -- **Always** pass `SSH_KEY_PATH` / `-i ` explicitly in scripted SSH; non-interactive shells have no ssh-agent identities. -- **Never** route audio or transcripts to cloud services — speech stays on the LAN. (Scrubbed text via `/scrub` is the only sanctioned path toward frontier models.) -- **Never** commit owner-specific hostnames, IPs, usernames, or names into package strings, UI text, or docs — this package gets shared; use placeholders (`` style). -- **Never** install `cuda-python` in `parakeet-asr` — crashes real decode on this GPU/CUDA-13 stack; full story in the audio-speech rule. - -## Current state - -- **Working (v0.18.0:0, installed and serving):** swap dashboard; chat / transcribe / diarize(+chunk) / TTS proxies; embeddings + rerank + hybrid search (Qdrant); `/scrub` + `/rehydrate`; label-merge incl. dual-channel mode. Spark 2 audio stack is healthy (11k+ requests/12h, all 200). -- **In progress — Signal Engine "flakiness":** diagnosed, not a server bug — transient 1–4s unresponsiveness while the single GPU is continuously busy. Remedy is client-side; a drafted message (in-flight cap 2, hard ceiling 3 global across audio endpoints, retry-with-backoff on timeout/503) is with the owner to forward to that dev. -- **Decided, not implemented:** remote access stays WireGuard/Tailscale split-tunnel — no public interface, so no API auth built; an empirical concurrency sweep is offered but needs the owner's explicit OK in a quiet window. -- **Known limits:** `/health` blips while the GPU is busy (mitigated client-side); dual-channel can miss a quiet local word under loud remote bleed; the connectivity log misses sub-5s outages between 5s polls; diarizer caps at 4 speakers. -- **Repo wart:** commit `367d986` is labeled `v0.13.0:4` but actually contains everything through v0.18.0:0 — per-version commits for v0.14–v0.18 are missing. Keep commit messages accurate going forward. -- **Hosting:** repo pushes to the owner's self-hosted Gitea — remote `gitea`, branch `master`, over SSH (host alias + key live in the local `~/.ssh/config`; no owner-specific details belong in the repo). Push there after committing. -- **Next:** (1) owner forwards the concurrency note to the Signal Engine dev; (2) run the concurrency sweep if the dev wants the measured knee; (3) add the `--memory` cap to parakeet-asr via the Reapply-patches action; (4) pick the next item from ROADMAP.md. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000..47dc3e3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/docs/guides/audio-speech.md b/docs/guides/audio-speech.md new file mode 100644 index 0000000..55b59fc --- /dev/null +++ b/docs/guides/audio-speech.md @@ -0,0 +1,35 @@ +--- +paths: + - "image/app/audio_proxy.py" + - "image/app/speech_models.py" + - "image/app/deep_health.py" + - "image/parakeet_patches/**" + - "scripts/test-audio-with-speakers.sh" + - "docs/AUDIO_API.md" +--- + +# Audio / speech stack (Parakeet STT + Sortformer diarizer + Kokoro TTS on Spark 2) + +## Changing the parakeet-asr container + +- `image/parakeet_patches/` (`main.py`, `diarizer.py`) is an overlay copied into the `parakeet-asr` container by the "Reapply speech-model patches" dashboard action (`image/app/speech_models.py`). This is the **only** durable way to change that container — `docker exec` / pip changes inside it die on `docker rm`. +- **Never install `cuda-python` in parakeet-asr** to "fix" the startup warning about CUDA graphs being disabled. The warning is harmless; enabling the graph path crashes real decode with illegal memory access on this GPU/CUDA-13 stack (GB10/sm_121). The slow path served 11k+ requests with zero failures — leave it alone. +- Pin/constrain torch versions when pip-installing anything into NGC-based containers on the Sparks (ABI breaks otherwise); expect ARM64 wheel gaps and source builds (`--no-build-isolation` for torchaudio). Applies to `spark_embed` too. + +## Testing audio endpoints + +- Test with **real speech** (e.g. `say -o /tmp/t.wav --data-format=LEI16@16000 ""`), not tones/silence — zero-token audio skips the decoder paths where crashes live. +- Send audio requests to Spark 2 **sequentially** in tests/scripts. Parallel audio requests can race (cuFFT → 503), and the single GPU serializes them anyway. +- End-to-end suite (hits the LIVE cluster): + +```bash +./scripts/test-audio-with-speakers.sh # from repo root +``` + +`SPARK_CONTROL` defaults to `http://127.0.0.1:9999` (a running local dev server); point it at the installed package URL otherwise. + +## API quirk + +Spark Control's `/v1/models` lists *audio* models (STT model + Kokoro voices) by design — **not** the loaded LLM. Discover the LLM via `/api/status` (`vllm.current_model`). + +Diarizer caps at 4 speakers (Sortformer `diar_sortformer_4spk-v1`). diff --git a/docs/guides/fastapi-image.md b/docs/guides/fastapi-image.md new file mode 100644 index 0000000..4eef500 --- /dev/null +++ b/docs/guides/fastapi-image.md @@ -0,0 +1,39 @@ +--- +paths: + - "image/**" +--- + +# FastAPI image (`image/`) + +Standalone FastAPI app (Python ≥3.11; ships on `python:3.12-slim`; UI on port 9999; vanilla HTML/CSS/JS, no framework). Python has no configured linter/formatter — match the style of the file you're editing. + +## Local dev (no StartOS) + +```bash +cd image +python3 -m venv .venv && source .venv/bin/activate # one-time +pip install -e . +export SPARK1_HOST= SPARK1_USER= SPARK2_HOST= SPARK2_USER= SSH_KEY_PATH= +# Required outside the container — these default to paths under /data, which only exists in the image +# (missing REDACTION_MAP_DB crashes startup; missing CONNECTIVITY_LOG 500s /api/status): +export REDACTION_MAP_DB=/tmp/redaction_maps.db CONNECTIVITY_LOG=/tmp/connectivity.json +uvicorn app.server:app --host 0.0.0.0 --port 9999 --reload +``` + +Other env vars: `BIND_PORT`, `MODELS_YAML`, `SSH_DIR`, `SSH_KNOWN_HOSTS`, `MODELS_OVERRIDES`, `SERVICES_OVERRIDES`. + +## Tests + +No pytest harness — each suite is a standalone script run with the `image/.venv` interpreter (system python3 has no deps). See the redaction and audio rules for the suites themselves. + +## Conventions + +- Pydantic request models go at **module scope**, never inside a `build_router()` body (FastAPI silently 422s otherwise). +- New external-facing endpoints get documented in `docs/` (`AUDIO_API.md`, `EMBEDDINGS.md`, `REDACTION_GATEWAY.md`) and noted in release notes. + +## Layout + +- `image/app/server.py` — FastAPI entry; routers live in sibling modules (`audio_proxy.py`, `llm_proxy.py`, `embeddings_proxy.py`, `redaction_gateway.py`, `swap.py`, `health.py`, `deep_health.py`, `connectivity.py`, …). +- `image/app/static/` — the dashboard UI. +- `image/models.yaml` — vLLM model catalog bundled into the image. +- `image/spark_embed/` — Dockerfile + app for the embeddings container; built ON a Spark (ARM64, NGC PyTorch base — see the audio/cluster rule for NGC torch-pinning caveats). diff --git a/docs/guides/redaction.md b/docs/guides/redaction.md new file mode 100644 index 0000000..028085c --- /dev/null +++ b/docs/guides/redaction.md @@ -0,0 +1,23 @@ +--- +paths: + - "image/app/redaction/**" + - "image/app/redaction_gateway.py" + - "docs/REDACTION_GATEWAY.md" +--- + +# Redaction (`/scrub` + `/rehydrate`) + +- `image/app/redaction/scrub.py` + `test_scrub_leak.py` are vendored **byte-for-byte** from the CRM repo (sha recorded in `redaction/__init__.py`). **Never edit them here** — change them in the CRM repo, re-vendor (`cp`), update the sha, re-run the leak test. +- The gateway around the vendored scrubber is `image/app/redaction_gateway.py`. Its token-map store lives on `/data` (`REDACTION_MAP_DB`, default `/data/redaction_maps.db`) and fails closed if it can't open — set the env var when running outside the container. + +## Test suites — both must pass before shipping ANY redaction change + +```bash +cd image +.venv/bin/python -m app.redaction.test_gateway # /scrub + /rehydrate acceptance; offline, no cluster needed +.venv/bin/python app/redaction/test_scrub_leak.py # vendored golden-file leak test; offline +``` + +Keep the leak test green against the vendored `scrub.py` after any re-vendor. + +Policy context: scrubbed text via `/scrub` is the **only** sanctioned path toward frontier/cloud models — see the whole-repo privacy rule in CLAUDE.md. diff --git a/docs/guides/startos-package.md b/docs/guides/startos-package.md new file mode 100644 index 0000000..bccc6e2 --- /dev/null +++ b/docs/guides/startos-package.md @@ -0,0 +1,31 @@ +--- +paths: + - "package/**" +--- + +# StartOS package (`package/`) + +TypeScript wrapper that ships the Docker image as an s9pk. `@start9labs/start-sdk` pinned `1.3.3`, Node ≥22, bundled by `@vercel/ncc`. + +## Commands + +```bash +cd package +npm i # one-time +make x86 # typecheck + ncc bundle + docker build + pack → spark-control_x86_64.s9pk +make install # sideload to the Start9 server; needs "host: http(s)://.local" in ~/.startos/config.yaml +npm run check # tsc --noEmit — run after any startos/ edit; make x86 also runs it +npm run prettier # prettier --write startos (no semicolons, single quotes, trailing commas) +``` + +`make aarch64` for ARM Start9 servers. `make install` picks the newest `*.s9pk` in `package/` and restarts the live spark-control service — get a go/no-go first. + +## Versioning & release notes + +- Version format is `X.Y.Z:N` (`:N` = revision). Bump in `package/startos/versions/v0_1_0.ts`; **replace** the release notes — never leave old notes behind under an extra key (any unknown key fails `tsc`). +- New external-facing endpoints get noted in release notes for downstream app developers (Recap Relay, Ten31 Transcripts, CRM, Signal Engine consume these APIs). + +## Layout + +- `package/startos/` — manifest, interfaces, actions (`configureSparks`, `showPublicKey`), `versions/v0_1_0.ts` (current version string + release notes). +- The "Reapply speech-model patches" action is **not** a StartOS action — it's a dashboard action implemented in `image/app/speech_models.py`.