From 01de4a1fe7db99246509b5032ded5b43b723a16c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 16 Aug 2026 15:32:18 +0000 Subject: [PATCH 1/7] Add Cloud Agent dev environment config Add repo-managed .cursor/environment.json plus install/start scripts so Cloud Agents boot fully set up for both the Python CLI/web/MCP package and the Rust core backend: - install.sh: ensure ffmpeg, add rustfmt, pip --user install the package with web/mcp/dev + test deps, build the rust-core release binary, and put ~/.local/bin on PATH for non-login shells. - start.sh: rebuild codec-carver-core if a fresh checkout wiped rust-core/target. - environment.json: run the FastAPI SaaS web service (saas_web:app) on 0.0.0.0:8000 and expose the port. Validated end-to-end: 631 Python tests, 22 Rust tests, cargo fmt, the codec-carver CLI (FLAC + Opus fallback), the codec-carver-library Rust bridge, and a browser upload through the web UI. Co-authored-by: Seongho Bae --- .cursor/environment.json | 15 +++++++++++++++ .cursor/install.sh | 28 ++++++++++++++++++++++++++++ .cursor/start.sh | 9 +++++++++ 3 files changed, 52 insertions(+) create mode 100644 .cursor/environment.json create mode 100755 .cursor/install.sh create mode 100755 .cursor/start.sh diff --git a/.cursor/environment.json b/.cursor/environment.json new file mode 100644 index 00000000..9091313a --- /dev/null +++ b/.cursor/environment.json @@ -0,0 +1,15 @@ +{ + "name": "Codec Carver", + "install": "bash .cursor/install.sh", + "start": "bash .cursor/start.sh", + "terminals": [ + { + "name": "web", + "command": "python3 -m uvicorn saas_web:app --host 0.0.0.0 --port 8000", + "description": "Codec Carver FastAPI upload service (saas_web:app) on http://0.0.0.0:8000" + } + ], + "ports": [ + { "name": "web", "port": 8000 } + ] +} diff --git a/.cursor/install.sh b/.cursor/install.sh new file mode 100755 index 00000000..18e69e1e --- /dev/null +++ b/.cursor/install.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Idempotent Cloud Agent setup for codec-carver. +# Runs from the repository root (/workspace) after each checkout. +set -euo pipefail + +# ffmpeg/ffprobe are required at runtime for probing and conversion. +# The default Cloud Agent base image already ships them; install only if missing. +if ! command -v ffmpeg >/dev/null 2>&1; then + sudo apt-get update + sudo apt-get install -y --no-install-recommends ffmpeg +fi + +# rustfmt backs `cargo fmt` (rust-toolchain.toml pins a minimal profile without it). +rustup component add rustfmt >/dev/null 2>&1 || true + +# Python package (codec-carver CLI) + web/mcp extras + dev/test deps +# (interrogate, hypothesis, coverage, atheris). Installed to the user site. +python3 -m pip install --user --disable-pip-version-check -e ".[dev,web,mcp]" -r requirements-dev.txt + +# Rust core binary (codec-carver-core) used by the audio-library CLI. +# A fresh checkout wipes rust-core/target, so (re)build it here. +cargo build --release --manifest-path rust-core/Cargo.toml + +# Expose pip --user console scripts (codec-carver, codec-carver-library, ...) in +# non-login shells too. Login shells already pick up ~/.local/bin via ~/.profile. +if ! grep -q '.local/bin' "$HOME/.bashrc" 2>/dev/null; then + printf '\nif [ -d "$HOME/.local/bin" ]; then PATH="$HOME/.local/bin:$PATH"; fi\n' >> "$HOME/.bashrc" +fi diff --git a/.cursor/start.sh b/.cursor/start.sh new file mode 100755 index 00000000..b158abb0 --- /dev/null +++ b/.cursor/start.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# Per-boot reconciliation: ensure the Rust backend binary exists after a fresh +# checkout (install is not re-run when a pod boots from a prebuilt environment). +set -euo pipefail +cd "$(dirname "$0")/.." + +if [ ! -x rust-core/target/release/codec-carver-core ]; then + cargo build --release --manifest-path rust-core/Cargo.toml +fi From e0ca48fb089142e8a436b4a20720b00cf11c32f5 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 16 Aug 2026 15:42:59 +0000 Subject: [PATCH 2/7] Auto-start web service in start.sh instead of terminals A prebuilt-build/snapshot boot does not always replay the environment.json terminals, so the FastAPI service could be down on boot. Launch it from start.sh (guarded single-instance, backgrounded, logs to /tmp/codec-carver-web.log) so it comes up on every boot mode, and drop the terminals entry to avoid a double bind on port 8000. Co-authored-by: Seongho Bae --- .cursor/environment.json | 7 ------- .cursor/start.sh | 14 ++++++++++++-- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/.cursor/environment.json b/.cursor/environment.json index 9091313a..d5d3c091 100644 --- a/.cursor/environment.json +++ b/.cursor/environment.json @@ -2,13 +2,6 @@ "name": "Codec Carver", "install": "bash .cursor/install.sh", "start": "bash .cursor/start.sh", - "terminals": [ - { - "name": "web", - "command": "python3 -m uvicorn saas_web:app --host 0.0.0.0 --port 8000", - "description": "Codec Carver FastAPI upload service (saas_web:app) on http://0.0.0.0:8000" - } - ], "ports": [ { "name": "web", "port": 8000 } ] diff --git a/.cursor/start.sh b/.cursor/start.sh index b158abb0..2dcb2dcd 100755 --- a/.cursor/start.sh +++ b/.cursor/start.sh @@ -1,9 +1,19 @@ #!/usr/bin/env bash -# Per-boot reconciliation: ensure the Rust backend binary exists after a fresh -# checkout (install is not re-run when a pod boots from a prebuilt environment). +# Per-boot reconciliation. install is not re-run when a pod boots from a +# prebuilt environment, so anything that must exist on every boot lives here. set -euo pipefail cd "$(dirname "$0")/.." +# 1) Ensure the Rust backend binary exists (a fresh checkout wipes rust-core/target). if [ ! -x rust-core/target/release/codec-carver-core ]; then cargo build --release --manifest-path rust-core/Cargo.toml fi + +# 2) Start the FastAPI SaaS web service (saas_web:app) if it is not already up. +# Backgrounded so `start` returns promptly; the guard keeps it a single instance +# and works on every boot mode (just-in-time or prebuilt build/snapshot). +# Logs: /tmp/codec-carver-web.log. Use `python3 -m uvicorn` so it is PATH-independent. +if ! curl -sf -o /dev/null http://127.0.0.1:8000/ 2>/dev/null; then + nohup python3 -m uvicorn saas_web:app --host 0.0.0.0 --port 8000 \ + > /tmp/codec-carver-web.log 2>&1 & +fi From 8a60d4aeaf4db080bfe8ce305a787b58b455d70a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 16 Aug 2026 15:53:36 +0000 Subject: [PATCH 3/7] Run web service from a terminal; keep start.sh for Rust reconcile Follow env-setup best practice: the FastAPI dev server belongs in a named terminal (visible logs, restartable) rather than backgrounded from start.sh. start.sh now only ensures the codec-carver-core binary exists after a fresh checkout; the 'web' terminal runs uvicorn saas_web:app on 0.0.0.0:8000. Co-authored-by: Seongho Bae --- .cursor/environment.json | 7 +++++++ .cursor/start.sh | 15 +++------------ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/.cursor/environment.json b/.cursor/environment.json index d5d3c091..a33f794c 100644 --- a/.cursor/environment.json +++ b/.cursor/environment.json @@ -2,6 +2,13 @@ "name": "Codec Carver", "install": "bash .cursor/install.sh", "start": "bash .cursor/start.sh", + "terminals": [ + { + "name": "web", + "command": "python3 -m uvicorn saas_web:app --host 0.0.0.0 --port 8000", + "description": "Codec Carver FastAPI upload UI (saas_web:app) at http://localhost:8000" + } + ], "ports": [ { "name": "web", "port": 8000 } ] diff --git a/.cursor/start.sh b/.cursor/start.sh index 2dcb2dcd..0bbdeb97 100755 --- a/.cursor/start.sh +++ b/.cursor/start.sh @@ -1,19 +1,10 @@ #!/usr/bin/env bash -# Per-boot reconciliation. install is not re-run when a pod boots from a -# prebuilt environment, so anything that must exist on every boot lives here. +# Per-boot reconciliation: ensure the Rust backend binary exists after a fresh +# checkout (install is not re-run when a pod boots from a prebuilt environment). +# The FastAPI web service runs from the "web" terminal in environment.json. set -euo pipefail cd "$(dirname "$0")/.." -# 1) Ensure the Rust backend binary exists (a fresh checkout wipes rust-core/target). if [ ! -x rust-core/target/release/codec-carver-core ]; then cargo build --release --manifest-path rust-core/Cargo.toml fi - -# 2) Start the FastAPI SaaS web service (saas_web:app) if it is not already up. -# Backgrounded so `start` returns promptly; the guard keeps it a single instance -# and works on every boot mode (just-in-time or prebuilt build/snapshot). -# Logs: /tmp/codec-carver-web.log. Use `python3 -m uvicorn` so it is PATH-independent. -if ! curl -sf -o /dev/null http://127.0.0.1:8000/ 2>/dev/null; then - nohup python3 -m uvicorn saas_web:app --host 0.0.0.0 --port 8000 \ - > /tmp/codec-carver-web.log 2>&1 & -fi From 9f22befc8d16af3e5cf978092866a5f10ff8836f Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 18 Aug 2026 01:45:51 +0000 Subject: [PATCH 4/7] Add auth-exempt GET /health readiness probe Cheap JSON liveness/readiness endpoint (no HTML render or subprocess work), exempt from API-key auth like GET /. Point the Docker HEALTHCHECK at it instead of the heavy HTML upload page, and cover it with tests (basic + auth -exempt). Enables a fast readiness probe for the environment start script. Co-authored-by: Seongho Bae --- Dockerfile | 4 ++-- saas_web.py | 28 +++++++++++++++++++++------- tests/test_saas_web.py | 12 ++++++++++++ 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index a7ee8ee8..574e9a60 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,8 +22,8 @@ USER appuser EXPOSE 8000 -# Liveness: the FastAPI app serves the upload UI at "/". +# Liveness: the FastAPI app serves a cheap JSON probe at "/health". HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ - CMD python -c "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8000/', timeout=4).status==200 else 1)" + CMD python -c "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=4).status==200 else 1)" CMD ["uvicorn", "saas_web:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/saas_web.py b/saas_web.py index 63265e94..64f64bc2 100644 --- a/saas_web.py +++ b/saas_web.py @@ -100,18 +100,20 @@ def get_configured_api_keys(): @app.middleware("http") async def require_api_key(request: Request, call_next): - """Enforce opt-in API-key authentication on all endpoints except GET /. + """Enforce opt-in API-key authentication on all endpoints except GET / and GET /health. When one or more keys are configured via CODEC_CARVER_API_KEYS, every - request other than GET / (the upload UI page) must carry an X-API-Key - header matching a configured key; comparison uses hmac.compare_digest to - stay constant-time. Requests failing the check receive a 401 JSON error - without echoing any key material. When no keys are configured, all - requests pass through unchanged. + request other than GET / (the upload UI page) and GET /health (the liveness + probe) must carry an X-API-Key header matching a configured key; comparison + uses hmac.compare_digest to stay constant-time. Requests failing the check + receive a 401 JSON error without echoing any key material. When no keys are + configured, all requests pass through unchanged. """ configured_keys = get_configured_api_keys() - if configured_keys and not (request.method == "GET" and request.url.path == "/"): + if configured_keys and not ( + request.method == "GET" and request.url.path in ("/", "/health") + ): provided_key = request.headers.get("x-api-key", "") if not any( hmac.compare_digest(provided_key, key) for key in configured_keys @@ -512,6 +514,18 @@ async def get_ui(): return HTML_TEMPLATE +@app.get("/health") +async def health(): + """Return a lightweight liveness/readiness probe. + + Auth-exempt (like GET /) and intentionally cheap — no HTML rendering or + disk/subprocess work — so the container HEALTHCHECK and the environment + start script can poll it as a fast readiness signal. + """ + + return {"status": "ok"} + + @app.post("/shrink") def shrink_media( background_tasks: BackgroundTasks, diff --git a/tests/test_saas_web.py b/tests/test_saas_web.py index 3b57e033..4e836654 100644 --- a/tests/test_saas_web.py +++ b/tests/test_saas_web.py @@ -37,6 +37,11 @@ def test_get_ui(self): self.assertEqual(response.status_code, 200) self.assertIn(b"Codec Carver SaaS", response.content) + def test_health_returns_ok(self): + response = client.get("/health") + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json(), {"status": "ok"}) + def test_get_ui_includes_accessible_file_input_helpers(self): response = client.get("/") self.assertEqual(response.status_code, 200) @@ -732,6 +737,13 @@ def test_get_ui_always_open_without_key(self): self.assertEqual(response.status_code, 200) self.assertIn(b"Codec Carver SaaS", response.content) + def test_health_always_open_without_key(self): + with patch.dict(os.environ, {"CODEC_CARVER_API_KEYS": "secret-key"}): + response = client.get("/health") + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json(), {"status": "ok"}) + def test_job_api_requires_key_when_configured(self): with patch.dict(os.environ, {"CODEC_CARVER_API_KEYS": "secret-key"}): response = client.get("/jobs/missing") From 7345975f41da9b073df3e53ac4f22bdfdd72a7c0 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 18 Aug 2026 01:45:51 +0000 Subject: [PATCH 5/7] Harden Cloud Agent install.sh (review feedback) - cd to repo root so pip/cargo act on the checked-out tree regardless of CWD. - Require both ffmpeg AND ffprobe before skipping the ffmpeg install. - Install rustfmt fail-closed (drop '|| true'): a missing formatter must break setup rather than ship format drift, since cargo fmt --check is a CI gate. - Install Python deps hash-locked (requirements-lock.txt + fuzz/requirements- dev.txt) then the package with -e . --no-deps, mirroring CI's supply-chain contract instead of resolving extras from the index unpinned. Build isolation stays on because this base image ships packaging 24.0 while setuptools 83 needs >=24.2. Co-authored-by: Seongho Bae --- .cursor/install.sh | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/.cursor/install.sh b/.cursor/install.sh index 18e69e1e..e8fa5864 100755 --- a/.cursor/install.sh +++ b/.cursor/install.sh @@ -1,25 +1,37 @@ #!/usr/bin/env bash # Idempotent Cloud Agent setup for codec-carver. -# Runs from the repository root (/workspace) after each checkout. set -euo pipefail -# ffmpeg/ffprobe are required at runtime for probing and conversion. -# The default Cloud Agent base image already ships them; install only if missing. -if ! command -v ffmpeg >/dev/null 2>&1; then +# Pin to the repository root so pip/cargo always act on the checked-out tree, +# regardless of the caller's working directory. +cd "$(dirname "$0")/.." + +# ffmpeg AND ffprobe are required at runtime for probing and conversion. The +# default Cloud Agent base image already ships them; install only if either is +# missing. +if ! command -v ffmpeg >/dev/null 2>&1 || ! command -v ffprobe >/dev/null 2>&1; then sudo apt-get update sudo apt-get install -y --no-install-recommends ffmpeg fi -# rustfmt backs `cargo fmt` (rust-toolchain.toml pins a minimal profile without it). -rustup component add rustfmt >/dev/null 2>&1 || true - -# Python package (codec-carver CLI) + web/mcp extras + dev/test deps -# (interrogate, hypothesis, coverage, atheris). Installed to the user site. -python3 -m pip install --user --disable-pip-version-check -e ".[dev,web,mcp]" -r requirements-dev.txt - -# Rust core binary (codec-carver-core) used by the audio-library CLI. -# A fresh checkout wipes rust-core/target, so (re)build it here. +# The pinned toolchain (rust-toolchain.toml) uses profile = minimal, which omits +# rustfmt. Add it fail-closed: `cargo fmt --check` is a CI gate, so a missing +# formatter must break setup rather than silently ship format drift. +# `cargo build` below auto-installs the pinned toolchain first if needed. cargo build --release --manifest-path rust-core/Cargo.toml +rustup component add rustfmt + +# Python dependencies follow CI's supply-chain contract: every runtime and +# dev/test dependency comes from the hash-locked sets, never resolved from the +# index. The package itself is then installed editable with --no-deps so no +# unpinned dependency is ever pulled. +python3 -m pip install --user --disable-pip-version-check --require-hashes -r requirements-lock.txt +python3 -m pip install --user --disable-pip-version-check --require-hashes -r fuzz/requirements-dev.txt +# Build isolation is left on (unlike CI's --no-index --no-build-isolation): this +# base image ships packaging 24.0, but setuptools 83 needs packaging>=24.2, so +# an isolated build environment supplies a compatible, version-pinned backend +# (from pyproject's build-system.requires) without touching runtime deps. +python3 -m pip install --user --disable-pip-version-check --no-deps -e . # Expose pip --user console scripts (codec-carver, codec-carver-library, ...) in # non-login shells too. Login shells already pick up ~/.local/bin via ~/.profile. From 2f7d907a1c8d71925e9b18f0a528b747415f5595 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 18 Aug 2026 01:45:51 +0000 Subject: [PATCH 6/7] Make web startup ready-or-fail in start.sh Move the FastAPI service from an environment.json terminal (which returns immediately, so a dead worker looks like a successful boot) into start.sh: launch uvicorn, then block until GET /health responds, failing fast if the worker exits or the probe times out. All probes are time-bounded so a port that accepts TCP but never answers HTTP cannot hang startup. Co-authored-by: Seongho Bae --- .cursor/environment.json | 7 ------- .cursor/start.sh | 43 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/.cursor/environment.json b/.cursor/environment.json index a33f794c..d5d3c091 100644 --- a/.cursor/environment.json +++ b/.cursor/environment.json @@ -2,13 +2,6 @@ "name": "Codec Carver", "install": "bash .cursor/install.sh", "start": "bash .cursor/start.sh", - "terminals": [ - { - "name": "web", - "command": "python3 -m uvicorn saas_web:app --host 0.0.0.0 --port 8000", - "description": "Codec Carver FastAPI upload UI (saas_web:app) at http://localhost:8000" - } - ], "ports": [ { "name": "web", "port": 8000 } ] diff --git a/.cursor/start.sh b/.cursor/start.sh index 0bbdeb97..b0f00280 100755 --- a/.cursor/start.sh +++ b/.cursor/start.sh @@ -1,10 +1,47 @@ #!/usr/bin/env bash -# Per-boot reconciliation: ensure the Rust backend binary exists after a fresh -# checkout (install is not re-run when a pod boots from a prebuilt environment). -# The FastAPI web service runs from the "web" terminal in environment.json. +# Per-boot startup for codec-carver: +# 1. ensure the Rust backend binary exists after a fresh checkout, and +# 2. bring up the FastAPI SaaS web service and BLOCK until it is ready. +# This is ready-or-fail: a worker that never binds or dies during startup makes +# `start` exit non-zero (a failed boot) instead of leaving the published :8000 +# port pointing at nothing. set -euo pipefail cd "$(dirname "$0")/.." +# 1) Rust backend binary used by the audio-library CLI (a fresh checkout wipes +# rust-core/target). if [ ! -x rust-core/target/release/codec-carver-core ]; then cargo build --release --manifest-path rust-core/Cargo.toml fi + +# 2) FastAPI SaaS web service on :8000. Idempotent: if it is already serving, +# there is nothing to do. Every probe is time-bounded so a port that accepts +# TCP but never answers HTTP cannot hang startup. +readiness_url="http://127.0.0.1:8000/health" +probe() { curl -fsS --connect-timeout 3 --max-time 5 -o /dev/null "$readiness_url" 2>/dev/null; } +if probe; then + exit 0 +fi + +# `python3 -m uvicorn` is PATH-independent; logs go to a stable path. +nohup python3 -m uvicorn saas_web:app --host 0.0.0.0 --port 8000 \ + > /tmp/codec-carver-web.log 2>&1 & +web_pid=$! + +# Wait for readiness; fail fast if the worker exits or never becomes ready. +for _ in $(seq 1 60); do + if ! kill -0 "$web_pid" 2>/dev/null; then + echo "codec-carver web worker (pid $web_pid) exited during startup" >&2 + cat /tmp/codec-carver-web.log >&2 || true + exit 1 + fi + if probe; then + exit 0 + fi + sleep 1 +done + +echo "codec-carver web service did not become ready on $readiness_url within 60s" >&2 +cat /tmp/codec-carver-web.log >&2 || true +kill "$web_pid" 2>/dev/null || true +exit 1 From e121e8f46c124b7aea3b5e29e04648258b2b7c20 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 18 Aug 2026 01:45:51 +0000 Subject: [PATCH 7/7] Document the .cursor/ Cloud Agent environment in AGENTS.md Co-authored-by: Seongho Bae --- AGENTS.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 090d7d2d..10e6b82b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -92,3 +92,26 @@ protection, `require_code_owner_review` in rulesets) are disabled across the Con org: there is a single maintainer (solo developer), so a code-owner approval gate can never be satisfied. This is ON HOLD until the org has multiple maintainers — do NOT re-enable these settings or add CODEOWNERS-based merge gates before then. + +## Cloud Agent environment (`.cursor/`) + +`.cursor/environment.json` is the repo-managed Cloud Agent config — the +highest-precedence environment source, so it applies to new agents with no +dashboard "Save". Keep the two lifecycle phases distinct: + +- `.cursor/install.sh` (`install`) — durable, idempotent, source-derived setup: + ensure `ffmpeg`/`ffprobe`, add the `rustfmt` component **fail-closed** (the + pinned toolchain is `profile = minimal`), install Python deps **hash-locked** + from `requirements-lock.txt` + `fuzz/requirements-dev.txt` and then the + package with `-e . --no-deps` (mirrors CI's supply-chain contract; never + resolve deps from the index unpinned), and build the `codec-carver-core` + release binary. It `cd`s to the repo root so it is CWD-independent. +- `.cursor/start.sh` (`start`) — per-boot reconciliation: rebuild the Rust + binary if a fresh checkout wiped `rust-core/target`, then start the FastAPI + service on `:8000` and **block until `GET /health` is ready (ready-or-fail)** + with time-bounded probes, so a dead worker is a failed boot rather than a + green start over a dead port. + +Put durable, source-derived setup in `install.sh`; put per-boot service startup +in `start.sh`. `GET /health` is a cheap, auth-exempt readiness probe used by +both `start.sh` and the Docker `HEALTHCHECK`.