test-convt-server ยท diff

git:20261005.4d18f7f to git:20261006.d9f6197

37 added, 20 removed. Audit B to A.

---
name: test-convt-server
- description: Run and check the convt cloud API (crates/convt-server, Axum) and worker (crates/convt-worker) locally. Use when changing either crate, adding an API route, or when asked to verify the cloud service.
+ description: Run and verify the convt jobs API, Postgres queue, object storage, sandbox worker and metering locally.
---
# Test the convt cloud service
- `convt-server` is an Axum API deployed to Railway. Implemented routes are `GET /health` (returns `ok`) and `GET /v1/formats` (JSON list of formats). Job upload and status routes are TODOs. `convt-worker` only logs which engines are ready and waits for Ctrl-C; its Postgres queue and object storage are not wired up. Neither needs credentials or a database today.
+ Read `docs/p9-cloud-plan.md` for the deployment contract. The API serves health, formats, OpenAPI and authenticated create, start, status, download and cancel operations. The worker claims Postgres leases and runs each conversion in a fresh Linux process sandbox by default, with Docker available as an optional backend. Neither service is deployed by these tests.
- ## Launch the server
+ ## Sandbox gate
- Railway injects `PORT`; locally pick a free port so two worktrees do not collide. The server binds `0.0.0.0`, so it is reachable from the network while it runs.
+ Build the Railway worker image from the bundled Linux payload, then run the escape and aggregate-resource probes before enabling uploads:
```sh
- work=$(mktemp -d /tmp/convt-server.XXXXXX)
- port=18080
- cargo build -p convt-server
- PORT=$port ./target/debug/convt-server >"$work/server.log" 2>&1 &
- echo $! > "$work/server.pid"
- for _ in $(seq 40); do curl -fsS "http://127.0.0.1:$port/health" && break; sleep 0.25; done
+ export PATH=$HOME/.cargo/bin:$HOME/.bun/bin:$PATH CONVT_LICENSE_STORE=file
+ docker build -f crates/convt-worker/deploy/railway/Dockerfile -t convt-railway-worker:local .
+ python3 crates/convt-worker/sandbox/process-gate.py
+ cargo build -p convt-server -p convt-worker
```
- Readiness: `/health` returns `ok` and `server.log` has a `listening on` line with your port. If the bind fails with "address in use", pick another port; do not kill the other listener.
+ The launcher scopes writable cgroups to its own labeled test container. It checks a fresh empty environment, separate UID, absent parent files and `/proc`, denied TCP and outside writes, Landlock, seccomp, capability removal, protected guardian, per-process limits, aggregate CPU and memory, unlinked storage exhaustion, cancellation, wall timeout and supervisor death. It verifies startup refusal when Landlock or seccomp is denied. Ordinary read-only cgroups or denied tmpfs mounts must also refuse startup. The explicit unsafe override reports missing protections; never enable it in production.
- ## Evidence
+ The default backend needs root setup operations, Landlock ABI 6, seccomp, writable delegated cgroup v2 controllers and private tmpfs mounts. Railway compatibility remains NOT CHECKED until the actual deployment passes `--sandbox-gate` and `--sandbox-test`. Only then set `CONVT_SANDBOX_VERIFIED=1` on the API. Docker's optional backend uses `sandbox/build.sh` and `sandbox/gate.py`, with `CONVT_SANDBOX_BACKEND=docker` and the intended image/runtime.
+ ## Local storage and launch
+
+ Use `scripts/db.sh up` and `bun run db:migrate` for the guarded per-checkout database. Build MinIO with `sandbox/build-minio.sh`, then run `crates/convt-worker/storage-dev.sh up`. The helper labels the checkout, chooses a random port, configures a one-day lifecycle and writes mode-0600 credentials under `.convt-dev`. Do not print that file. The helper's `down` command removes only its labeled container.
+
+ Source the storage environment, use `scripts/db.sh url server` for `DATABASE_URL`, and set a random `CONVT_WEB_TOKEN_SECRET` of at least 32 characters. Give the web Worker the same secret and `CONVT_API_URL`. Launch `target/debug/convt-server` and `target/debug/convt-worker` with separate retained PID files and logs. Choose an unused `PORT` and `CONVT_BIND_HOST`; the default API listener is loopback. Follow the remote-preview skill before starting or sharing a Tailscale preview. Bound readiness waits and stop only owned PIDs.
+
+ Readiness requires `/health` returning `ok`, a matching migration hash, and worker startup verifying the confined registry supports the advertised formats (or the image ID for the Docker backend). `/openapi.json` is the canonical jobs contract. Upload exactly the declared byte count using the returned PUT URL, whose signature binds Content-Length, then call start; only the sealed, size-checked object enters the queue.
+
+ ## Automated checks
+
```sh
- curl -fsS "http://127.0.0.1:$port/v1/formats" | python3 -c 'import json,sys; d=json.load(sys.stdin); print(len(d), d[0])'
- curl -s -o /dev/null -w '%{http_code}\n' "http://127.0.0.1:$port/nope" # expect 404
+ cargo test
+ bun run rs:check
+ bun run db:ci
```
- For a new route, test the success case, an error case, and the response shape. For anything CI should keep checking, write an in-process test that calls `app()` with `tower::ServiceExt::oneshot` (add `tower` with the `util` feature as a dev-dependency; it is not one yet).
+ `db:ci` creates a disposable labeled Postgres, checks migration drift and down files, exercises role grants and integration tests, verifies sqlx offline data and builds without a database. Server database tests must not silently skip:
- ## Worker
+ ```sh
+ bash packages/db/scripts/test-db.sh env CONVT_REQUIRE_DB=1 cargo test -p convt-server
+ ```
+ Cover cross-account read and download denial, stale lease completion after replacement, concurrent reservations at the cap, signed upload length, staging and sealed size limits, cancelled upload commitments until successful cleanup, concurrent API and Pro storage reservations, expiry fencing, monthly Pro allowance for annual subscriptions, and idempotent meter delivery. Worker tests reject archive links and traversal. New SQL uses checked sqlx macros. After editing queries, regenerate metadata:
+
```sh
- ./target/debug/convt-worker # or: cargo run -p convt-worker
+ bash packages/db/scripts/test-db.sh bash -c 'cd crates/convt-server && DATABASE_URL="postgresql://convt_owner:$TEST_OWNER_PASSWORD@127.0.0.1:$TEST_PG_PORT/convt_template" cargo sqlx prepare -- --all-targets'
```
- Expect one `engine ready` line per engine and `engine unavailable` warnings for the rest, matching `./target/debug/convt engines`. Stop it with Ctrl-C, or `kill -INT` the PID you started.
+ ## Real artifact checks
- ## Cleanup
+ Run `crates/convt-worker/examples/replica_acceptance.rs` in a gate-approved image with a disposable database from `packages/db/scripts/test-db.sh`. It starts one actual API and three worker binaries on the same queue, converts real image, SVG, video, PDF and document inputs, checks all three lease owners, attempt fencing, signatures and exactly one usage fact per job, then checks an empty queue. Pass a long MP4 through `CONVT_ACCEPTANCE_CANCEL_FIXTURE` to observe and cancel the actual process tree. Use the optional private SDK exchange for a separate Bun or Node client converting four real types. Never print API or storage credentials.
- `kill "$(cat "$work/server.pid")"`, then `rm -rf "$work"`. Never deploy to Railway or point the service at a real database or bucket without the user's explicit authorization.
+ Run `@convt/sdk` unit tests and inspect downloaded signatures and artifacts. The process gate kills an owned supervisor with SIGKILL and checks the guardian removes the whole group. Terminate owned stack workers with SIGTERM and assert their groups and resource domains are cleaned. Never signal a worker you did not start. Meter tests use a mock and verify `external_id`, customer, quantity, version header and duplicate acknowledgement. Real Polar metering requires provider credentials and an invoice check.
+
+ Run `convt-server`'s `bucket_acceptance` example with a bounded timeout against MinIO and separately Railway Buckets. It checks exact upload length, CORS headers, expiry and scoped deletion. A header check does not replace an actual browser upload. Actual Railway kernel and bucket tests remain NOT CHECKED until deployment.
+
+ The dashboard browser checks are in test-convt-web. Record logs and screenshots outside the repository, with PASS, FAIL or NOT CHECKED for each requested result. Shut down owned services and containers after temporary verification. Leave the shared Postgres and Mailpit running.