Repository navigation
fix(env): fail-closed Cloud Agent start and hash-locked install #427
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
cursor
wants to merge
7
commits into
main
Choose a base branch
from
cursor/bc-ddbe8a70-5cc2-4231-8e8c-27ff88db1949-24fd
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from 3 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
01de4a1
Add Cloud Agent dev environment config
cursoragent e0ca48f
Auto-start web service in start.sh instead of terminals
cursoragent 404b1b9
fix(env): fail-closed Cloud Agent start and hash-locked install
cursoragent 58a43b4
test(env): reject unhashed Cloud Agent dev installs
seonghobae 43894db
fix(env): remove unhashed Cloud Agent dev resolution
seonghobae 9d087dc
test(env): require Docker health probe to use /health
seonghobae 36493d4
fix(env): align container liveness with cheap health probe
seonghobae File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| { | ||
| "name": "Codec Carver", | ||
| "install": "bash .cursor/install.sh", | ||
| "start": "bash .cursor/start.sh", | ||
| "ports": [ | ||
| { "name": "web", "port": 8000 } | ||
| ] | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| #!/usr/bin/env bash | ||
| # Idempotent Cloud Agent setup for codec-carver. | ||
| # Runs after each checkout. Always resolve the repository root from this file | ||
| # so a cached or non-root working directory cannot pip/cargo the wrong tree. | ||
| set -euo pipefail | ||
| cd "$(dirname "$0")/.." | ||
| export PATH="${HOME}/.local/bin:${PATH}" | ||
|
|
||
| # 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). | ||
| # Fail closed: CI and local agents both run `cargo fmt --check`. | ||
| if ! command -v rustup >/dev/null 2>&1; then | ||
| echo "rustup is required so rustfmt can be added for cargo fmt checks." >&2 | ||
| exit 1 | ||
| fi | ||
| rustup component add rustfmt | ||
|
|
||
| # Match CI: hash-locked runtime deps, then the editable tree without | ||
| # re-resolving from the index, then unpinned-but-marked dev extras. | ||
| python3 -m pip install --user --disable-pip-version-check \ | ||
| --require-hashes -r requirements-lock.txt | ||
| python3 -m pip install --user --disable-pip-version-check \ | ||
| --no-index --no-deps --no-build-isolation -e . | ||
| python3 -m pip install --user --disable-pip-version-check \ | ||
| -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 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| #!/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. | ||
| # `start` must reach a clear ready-or-fail state: a green exit with a dead | ||
| # uvicorn leaves the published :8000 port pointing at nothing. | ||
| set -euo pipefail | ||
| cd "$(dirname "$0")/.." | ||
| export PATH="${HOME}/.local/bin:${PATH}" | ||
|
|
||
| WEB_HEALTH_URL="${CODEC_CARVER_WEB_HEALTH_URL:-http://127.0.0.1:8000/health}" | ||
| WEB_READY_ATTEMPTS="${CODEC_CARVER_WEB_READY_ATTEMPTS:-40}" | ||
| WEB_LOG="${CODEC_CARVER_WEB_LOG:-/tmp/codec-carver-web.log}" | ||
|
|
||
| # 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 | ||
|
|
||
| _web_is_ready() { | ||
| curl -sf -o /dev/null "${WEB_HEALTH_URL}" | ||
| } | ||
|
|
||
| # 2) Start the FastAPI SaaS web service (saas_web:app) if it is not already up. | ||
| # Backgrounded so `start` returns after readiness; the probe keeps it a single | ||
| # instance on every boot mode (just-in-time or prebuilt build/snapshot). | ||
| if _web_is_ready; then | ||
| exit 0 | ||
| fi | ||
|
|
||
| nohup python3 -m uvicorn saas_web:app --host 0.0.0.0 --port 8000 \ | ||
| > "${WEB_LOG}" 2>&1 & | ||
| web_pid=$! | ||
|
|
||
| attempt=0 | ||
| while [ "${attempt}" -lt "${WEB_READY_ATTEMPTS}" ]; do | ||
| if _web_is_ready; then | ||
| exit 0 | ||
| fi | ||
| if ! kill -0 "${web_pid}" 2>/dev/null; then | ||
| echo "uvicorn exited before becoming ready; see ${WEB_LOG}" >&2 | ||
| exit 1 | ||
| fi | ||
| attempt=$((attempt + 1)) | ||
| sleep 0.25 | ||
| done | ||
|
|
||
| echo "uvicorn did not become ready after ${WEB_READY_ATTEMPTS} probes; see ${WEB_LOG}" >&2 | ||
| exit 1 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,134 @@ | ||
| # Architecture | ||
|
|
||
| Codec Carver is the speech/video conversion module for STT and omni-modal LLM | ||
| input. It must run **standalone** (CLI, FastAPI upload service, MCP tool) and | ||
| as a **git submodule** consumed by naruon and sibling ContextualWisdomLab | ||
| services. | ||
|
|
||
| ## Runtime surfaces | ||
|
|
||
| ```mermaid | ||
| flowchart LR | ||
| subgraph standalone [Standalone] | ||
| CLI["codec-carver CLI"] | ||
| WEB["saas_web FastAPI"] | ||
| MCP["mcp_driver shrink_media"] | ||
| end | ||
| subgraph core [Conversion core] | ||
| MS["media_shrinker.convert_file"] | ||
| RS["rust-core codec-carver-core"] | ||
| end | ||
| CLI --> MS | ||
| WEB --> MS | ||
| MCP --> MS | ||
| CLI --> RS | ||
| WEB --> JOB["job_store SQLite WAL"] | ||
| MS --> FF["ffmpeg / ffprobe"] | ||
| ``` | ||
|
|
||
| | Surface | Module | Buyer action | | ||
| | --- | --- | --- | | ||
| | CLI | `media_shrinker.py` | Carve a folder of recordings into size-capped FLAC/Opus. | | ||
| | Library CLI | `audio_library.py` → Rust | Inventory, hash, TMK, and GPU transcription batches. | | ||
| | Web | `saas_web.py` | Upload one file or a batch; poll `/jobs/{id}` for long work. | | ||
| | MCP | `mcp_driver.py` | Call `shrink_media` from an agent runtime. | | ||
| | Jobs | `job_store.py` | Survive process restart; do not keep results only in RAM. | | ||
|
|
||
| Sources are never overwritten. Generated names keep the original filename plus | ||
| a new suffix (`meeting.wav.flac`). | ||
|
|
||
| ## Conversion pipeline | ||
|
|
||
| ```mermaid | ||
| flowchart TD | ||
| A[find_candidates] --> B[probe_media / ffprobe] | ||
| B --> C{duration over cap?} | ||
| C -->|yes| D[silencedetect + build_segments] | ||
| C -->|no| E[single segment] | ||
| D --> F[build_audio_plan FLAC] | ||
| E --> F | ||
| F --> G{_execute_plan} | ||
| G -->|FLAC over target| H[build_opus_plan] | ||
| H --> G | ||
| G --> I[preserve_file_attributes] | ||
| I --> J[write_report JSON] | ||
| ``` | ||
|
|
||
| Silence splits prefer long quiet intervals; a hard split just under the | ||
| duration cap is the fallback. Parsers of ffmpeg/ffprobe text | ||
| (`parse_silencedetect_intervals`, `_parse_probe_payload`) treat that text as | ||
| untrusted input and raise `MediaShrinkerError` rather than unexpected types. | ||
|
|
||
| ## Cloud Agent environment | ||
|
|
||
| `.cursor/environment.json` is the highest-precedence environment source for | ||
| Cloud Agents. Do not add `$schema` (the public schema rejects undeclared | ||
| fields). | ||
|
|
||
| | Phase | Script | Must do | | ||
| | --- | --- | --- | | ||
| | `install` | `.cursor/install.sh` | `cd` to repo root; hash-locked pip; fail-closed rustfmt; release-build Rust. | | ||
| | `start` | `.cursor/start.sh` | Rebuild Rust only if the binary is missing; start uvicorn; **wait on `GET /health`**. | | ||
| | ports | `8000` / `web` | Bind `0.0.0.0:8000` so the published port reaches the SaaS UI. | | ||
|
|
||
| Decision record: [`docs/doctoring/cloud-agent-environment.md`](docs/doctoring/cloud-agent-environment.md). | ||
|
|
||
| ## Job-store data (current) | ||
|
|
||
| `job_store.py` persists async conversions. Table `jobs` is a single-word name | ||
| and is **known debt**: the org naming rule requires two-or-more-word | ||
| snake_case objects (`conversion_jobs`) plus a 3NF-preserving rename migration. | ||
| Do not add more single-word tables. Columns already use multi-word names where | ||
| they store paths (`output_path`, `output_name`, `temp_dir`). | ||
|
|
||
| ```mermaid | ||
| erDiagram | ||
| JOBS ||--o| CONVERSION_OUTPUT : produces | ||
| JOBS { | ||
| text id PK | ||
| text status | ||
| text created_at | ||
| text updated_at | ||
| text output_path | ||
| text output_name | ||
| text error | ||
| text temp_dir | ||
| } | ||
| ``` | ||
|
|
||
| Callers pass `now` explicitly. The store never calls `datetime.now()`. | ||
|
|
||
| ## Security and operability baseline | ||
|
|
||
| - ffmpeg/ffprobe: `-nostdin` and `-protocol_whitelist file,crypto,data`. | ||
| - Uploaded names: sanitize to a safe basename; normalize Windows separators. | ||
| - Permissions copied onto outputs drop setuid/setgid/sticky bits. | ||
| - Opt-in API keys (`CODEC_CARVER_API_KEYS`) are a **known deviation**. Runtime | ||
| reads must move to a SQLite/KV credential registry; env is bootstrap | ||
| transport only (issues #329, #373). | ||
| - `GET /` and `GET /health` stay reachable without a key so a buyer can open | ||
| the form and a probe can confirm liveness. | ||
| - PII in recordings is the product. Do not mask audio content; protect it with | ||
| access control, retention, and encryption at rest instead. | ||
|
|
||
| ## Ecosystem connectors (leverage order) | ||
|
|
||
| 1. **naruon** — consume carved FLAC/Opus + JSON reports as speech/DOM input. | ||
| 2. **contextual-orchestrator** — route transcription/LLM describe calls; do not | ||
| read provider keys from raw env at runtime. | ||
| 3. **wardnet** — place the SaaS UI behind the org WAF/APIM when exposed. | ||
| 4. **keyverse** — replace shared API keys with passwordless service identity. | ||
| 5. **clearfolio / newsdom-api** — attach transcripts and reports to documents. | ||
|
|
||
| Each connector must keep this repo independently runnable. | ||
|
|
||
| ## Test and research baseline | ||
|
|
||
| - Unit/integration: `python3 -m unittest discover -s tests -v` (100% coverage | ||
| over the configured sources; 100% interrogate on production modules). | ||
| - Reality checks: WAV → FLAC lossless; tiny `--target-bytes` → Opus; silence | ||
| splits on long fixtures; Rust inventory SHA-256. | ||
| - Fuzz: Atheris harnesses in `fuzz/` plus Hypothesis mirrors in | ||
| `tests/test_fuzz_properties.py`. | ||
| - Speech/codec changes attach APA 7th citations under `docs/doctoring/` and | ||
| `docs/papers/` (PDF only when redistribution is permitted). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| # Cloud Agent environment bootstrap | ||
|
|
||
| ## Decision | ||
|
|
||
| Codec Carver commits a repository-managed Cloud Agent environment at | ||
| `.cursor/environment.json`. `install` installs hash-locked Python dependencies, | ||
| adds `rustfmt` to the pinned minimal Rust toolchain, and builds | ||
| `codec-carver-core`. `start` rebuilds that binary only when a fresh checkout | ||
| wiped `rust-core/target`, then brings up `saas_web:app` on `0.0.0.0:8000` and | ||
| **waits** until `GET /health` succeeds. | ||
|
|
||
| Agents should treat a green `start` as “the upload UI is listening.” A | ||
| `nohup` without a readiness wait is not an environment start. | ||
|
|
||
| ## Technical basis | ||
|
|
||
| Cursor resolves environment configuration from the checked-out | ||
| `.cursor/environment.json` before any dashboard-managed personal or team | ||
| environment (Cursor, n.d.). The public schema rejects undeclared fields, | ||
| including `$schema`, so this repository does not add one. | ||
|
|
||
| Python package integrity follows pip hash-checking mode: install the lock | ||
| file with `--require-hashes`, then link the editable tree with `--no-index | ||
| --no-deps` so the index cannot substitute a different wheel (Python Software | ||
| Foundation, n.d.). That pairing is the same contract CI uses and is the | ||
| minimum SLSA-aligned control this repository can apply without a hermetic | ||
| build service (SLSA, 2023). | ||
|
|
||
| Fail-closed toolchain setup follows NIST SSDF PW.4 / PW.8: produce a | ||
| repeatable build environment and do not continue when a required verification | ||
| tool (`rustfmt`) is missing (Souppaya et al., 2022). | ||
|
|
||
| `GET /health` is a liveness probe, not a substitute for job-store or ffmpeg | ||
| readiness. Load balancers and Cloud Agent `start` share that URL so a crashed | ||
| uvicorn cannot look like a successful boot. | ||
|
|
||
| ## Verification and rollback | ||
|
|
||
| - `bash -n .cursor/install.sh .cursor/start.sh` must succeed. | ||
| - `tests/test_cloud_agent_environment.py` locks the JSON fields, the hash-locked | ||
| pip lines, rustfmt fail-closed install, and the `/health` wait. | ||
| - `GET /health` stays auth-exempt and returns | ||
| `{"status": "ok", "service": "codec-carver"}` whether or not API keys are | ||
| configured. | ||
| - Roll back by restoring the previous scripts; do not reintroduce `|| true` on | ||
| rustfmt or a `nohup` that returns before the probe succeeds. | ||
|
|
||
| ## Next action | ||
|
|
||
| After this lands, mark the Cloud Agent environment draft ready for review and | ||
| close the earlier environment-only PR that lacked the readiness contract. | ||
|
|
||
| ## References | ||
|
|
||
| Cursor. (n.d.). *Cloud Agents environment schema*. Retrieved August 16, 2026, | ||
| from https://cursor.com/schemas/environment.schema.json | ||
|
|
||
| Python Software Foundation. (n.d.). *Secure installs: Hash-checking mode* | ||
| (pip documentation). Retrieved August 16, 2026, from | ||
| https://pip.pypa.io/en/stable/topics/secure-installs/ | ||
|
|
||
| SLSA. (2023). *Supply-chain Levels for Software Artifacts (SLSA) v1.0*. | ||
| https://slsa.dev/spec/v1.0/ | ||
|
|
||
| Souppaya, M., Scarfone, K., & Dodson, D. (2022). *Secure Software Development | ||
| Framework (SSDF) Version 1.1: Recommendations for mitigating the risk of | ||
| software vulnerabilities* (NIST Special Publication 800-218). National | ||
| Institute of Standards and Technology. https://doi.org/10.6028/NIST.SP.800-218 |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.