Todo 20 — multi-stage Dockerfile, WASM bundle-size gate, NEWS_DATABASE_URL support Completed: 2026-09-01 Scope - Added `/home/connor/docs/projects/news/Dockerfile` with three stages mirroring `runway/Dockerfile` exactly: - `web`: `rust:1.89-slim-bookworm`, Node 22 copied from a pinned `node:22-bookworm-slim` image, Trunk 0.21.14 installed as a released binary, `npm ci` at the workspace root, then `trunk build --release` in `crates/news-web`. - `server`: `cargo build --release --locked -p news-server`, binary copied out of the cache mount to `/news-server`. - `runtime`: `debian:bookworm-slim` with only `ca-certificates` and `tzdata`, server binary at `/usr/local/bin/news-server`, frontend dist copied to `/srv/dist`, `/db` created, `NEWS_DATABASE_URL=sqlite:///db/news.db`, `EXPOSE 3000`, `CMD ["news-server"]`. - Added `.dockerignore` mirroring runway's, with `crates/news-web/dist` and `crates/news-web/styles/generated.css` excluded. - Wired `NEWS_DATABASE_URL` as the primary database path source in `crates/news-server/src/bin_support.rs`, stripping the `sqlite://` prefix to obtain the filesystem path while preserving the existing create-if-missing behavior. Falls back to `NEWS_DB_PATH`, then `./news.db`. Existing behavior without the new variable is unchanged. - Added a clap `version` flag (`#[command(version)]`) to `news-server` so the container can be smoke-tested with `news-server --version`. - Replaced the stub `bundle` job in `.gitea/workflows/ci.yml` with the runway- matching job: install Trunk, `npm ci`, `trunk build --release` in `crates/news-web`, then a 1.8 MiB WASM size assertion that fails with the byte overage printed. Verification - `cargo fmt --all --check` exit 0. - `cargo clippy --workspace --all-targets -- -D warnings` exit 0. - `cargo test --workspace` green (89 tests: 87 existing + 2 new `bin_support::tests` for `NEWS_DATABASE_URL` parsing). Container acceptance - `podman build -t news-triage:test /home/connor/docs/projects/news` exited 0. - `podman run --rm news-triage:test news-server --version` output: `news-server 0.1.0` (no missing shared library error). QA happy path - `podman run --rm news-triage:test ls /srv/dist` output: ``` generated-f134b848b9e82d1f.css index.html news-web-8411e96eeb0e2268.js news-web-8411e96eeb0e2268_bg.wasm ``` QA failure path (temporary wasm inflation, fully reverted) - Temporarily added a 2,000,000-byte `INFLATE` static array to `crates/news-web/src/lib.rs` and referenced it from `app.rs`. - Rebuilt with `trunk build --release`, then ran the CI budget-check shell logic: ``` crates/news-web/dist/news-web-9c5e74ba255bb294_bg.wasm is 2537078 bytes (budget 1800000, v1 shipped 2500000) over budget by 737078 bytes ``` - Reverted both source files; `git diff` shows no changes to tracked files. - Post-revert rebuild produced: ``` crates/news-web/dist/news-web-fdfd44ef4a5bb38e_bg.wasm is 537081 bytes (budget 1800000, v1 shipped 2500000) ``` Deviations from runway's pattern - Rust base image is `rust:1.89-slim-bookworm` instead of runway's 1.98, matching this repo's pinned `rust-toolchain.toml`. - The runtime `ENV` only sets `NEWS_DATABASE_URL`; runway also sets `RUNWAY_BIND`. `news-server` already binds `0.0.0.0:3000` by default, so no extra env var is needed for the documented container behavior. - Runtime comment updated to mention the digest scheduler and quiet-hour gate rather than calendar recurrence, reflecting news-triage's domain. Evidence files - `.omo/evidence/task-20-news-triage.txt` — this file.