Releases ·
Roastwarden 0.3.0
Everything since v0.2.0 (2026-10-23): rustRoast for daily use. Roast guidance reads each
roast against its profile and references; green coffee with purchases, costs, supplier
links and a shopping list; the roast sequence; nightly backups with a checked restore;
tokens, approvals and an audit log; notifications to a phone; the roast command line and
an agent skill; an OpenAPI document from the code; a user guide. Releases now carry roast
for Linux x86_64 and aarch64.
Added
-
User guide (User guide): setting up, roasting, History, profiles, beans and costs, agents and tokens, the audit log, backups and restore, with screenshots.
-
Releases with the
roastbinaries (.forgejo/workflows/release.yml, after svrctlrs): av*tag that equals every crate's and the dashboard's version publishes a Forgejo release with its CHANGELOG section androastfor Linux x86_64 and aarch64 with SHA-256 sums (docs/DEVELOPMENT.md, "Releasing").scripts/ci/check-version.shchecks a tag before it is pushed; a push toci/releaseis a dry run. -
Audit log filters and search (Control, "Audit log"): every entry has a kind (control, roast, profile, beans, roaster, settings, access, backup, other); Settings → Audit filters by kind, roaster, token and "refused or failed", and searches the path, body, token, user, address and client, on the server, so "Older" pages through the matches.
GET /api/audit?kind=&q=&token=&failed=,roast audit --kind --find --by --failed. -
Purchases and what the coffee cost (Green coffee, migration 027): a purchase records price, shipping, fees, currency, supplier, crop year and arrival; an order of several beans splits its shipping by weight (Beans → Record an order,
POST /api/purchases); ledger rows can be edited or removed. A bean's cost per kg is the weighted average of what is left. Each roast shows its coffee cost, per kg roasted and per espresso and filter dose; Beans → Spending sums each month. Settings → Roasting sets the currency, doses and bag size.roast bean stock add … --price --shipping --currency,roast costs, androast showwith the roast's cost. -
Add from a supplier's link and a shopping list (Green coffee, migrations 028–029): Beans → Add from a link previews a coffee from Sweet Maria's, Bodhi Leaf, Theta Ridge, Coffee Bean Corral, Showroom, Happy Mug or Burman (specs, score, every bag size's price; only those https hosts are fetched, adapters tested on saved pages) and saves it as a bean, a shopping-list entry or a purchase. "Bought" on the shopping list adds the bean and its priced purchase. Beans keep the supplier's details (region, producer, altitude, grade, screen, certifications, score, link, the rest as labels), and the bean form suggests earlier values plus seeded countries, processes and varieties.
roast bean import <link> [--want|--bought SIZE],roast wanted. -
roastcommand line (The roast CLI, after svrctlrs'ssvrctl): status, roasts and one roast with its events, beans, stock purchases and corrections (5kg,2lbread as grams), the roast plan, profiles, the audit log, backups, the OpenAPI document and any API call, with an API token. Compact JSON without nulls on a pipe, text on a terminal, errors with a code and a hint, and fixed exit codes (0 done … 3 a human must act … 7 unreachable).backupandrestorerun next to the database without the server (docker exec rustroast roast backup). The image ships it and usesroast healthas its healthcheck. The server crate is now a library with two binaries. -
inventorytoken preset (Control, "Tokens"): reads, beans, stock and the roast plan, nothing on a roaster or a roast, so an agent that records a purchase needs no admin token. A test checks that every route a preset allows is in the OpenAPI document. -
Approvals (Control, "Approvals", after svrctlrs): a token that asks first (the default for new tokens) cannot start a roast on a physical roaster or delete a roast, bean, profile or roaster until the owner approves it in Settings → Approvals. The request goes out through every notification channel with a link; the call waits an hour, and on approval runs as the token through every check again.
roastexits 3 for a held call and waits with--wait;roast bean rmandroast approval. Settings → Tokens sets "ask first" per token. -
Agent skill
skills/rustroast/SKILL.md(Hermes, Claude Code) and a Hermes eval (scripts/eval/hermes/, five tasks from "I bought 5 kg of Ethiopia Guji" to a roast start on the real roaster): 6/6 with a cloud model and with local Qwen. -
roastlists are for finding, as svrctl's: no timestamps, descriptions or user agents (profile showhas a profile's);beans --low;openapi --find <word>lists only the matching calls instead of the 320 KB document. The skill is linked for Claude Code in.claude/skills/as svrctlrs's is. -
OpenAPI document from the code (
GET /api-docs/openapi.json, Swagger UI at/docs, committed asdocs/openapi.json): every/api/operation with its parameters, request and response types and errors, in place of the hand-written document that covered 28 routes. A route is served and documented by the same line, and tests fail when the document is stale or an/api/route is left out (docs/DEVELOPMENT.md, "OpenAPI"). -
Roast sequence per roaster (Roast sequence): Charge turns heater power on, sets the fan and starts Profile drive; Drop stops the drive, cuts heater power and cools until BT and ET are below 45 °C, then the fan stops and Cool end is marked. Each step can be switched off under Roasters → the roaster → Control; the step card says what Charge and Drop will do.
-
Cancel a roast from the Roast page: the drive stops, heater power goes off, a hot chamber cools, and nothing is saved.
-
Resume after a heater cut: when the roaster cuts its heater mid-drive, the Roast page says so (with the firmware's reason from 3.3.2 on) and offers one tap to switch heater power on and resume Profile drive; the roaster is left in Manual at 0 % meanwhile.
-
Undo in the toast after marking a landmark (not for Charge and Drop, which act on the roaster).
-
Beans and planned roasts (Planned roasts and batches): a Beans section with green stock (a ledger of purchases, corrections and roasts, with a low-stock note) and a queue of planned roasts (bean, green weight, profile, roaster). The Roast page offers the next planned roast ("Start planned roast", Skip, Unplanned) and a bean picker that says what the roast leaves. A roast from a bean takes its green weight off the stock; Cancel gives it back.
-
Batch numbers per roaster, from 1 (existing roasts numbered by date), shown in History (sortable
#column, search#12), on the roast, the report and the Roast page; editable; exported to Artisan asroastbatchnr. -
Profile guidance: the profile page shows the level among the colour tiles, the results it should give and how the profile's roasts ran on the selected roaster, with what to edit (first crack against the profile's mark, development, loss and colour); the profile editor judges the curve's development against the level as you edit and shows the phase split. RoR on the History roast chart. Four more seed profiles: Extra-light filter (Nordic), Cupping (SCA sample roast), Light espresso with a short development, Medium-light omni (migration 021).
-
Backups and restore (Running it, "Backups"): a nightly
VACUUM INTOcopy of the database with retention (14 by default, at 03:00 UTC, delayed while a roast runs), and Settings → Backups with the last copy, the next run, Back up now, a Check of any copy and a Restore that checks it and puts it in place at the next start (the old database is moved aside). A restore drill is inDEPLOY.md. -
Roast guidance (Roast guidance): the app reads a roast's numbers against cited, dated references and this roaster's own history. Profiles carry a roast level (the SCA colour tiles on ground Agtron), a purpose and optional expected ranges; the History roast page shows up to three lines naming only what is off and a thin range band beside weight loss, colour, development, total time and FC / drop temperature, each with its basis and sources; the report prints them, the History table marks values outside with a dot, the Roast page shows the expected development window and, after Complete, takes the weights and shows the summary. Temperatures and times are judged only against the profile's own roasts (from three).
GET /api/profiles/{id}/history; migration 019. -
Statistics (Statistics): AUC per phase and the RoR slope from first crack to drop on a roast's page, its report and Compare; Compare's consistency band (mean ± σ of BT and RoR), BT spread and a mean ± σ row; live AUC against the profile's in the Roast page's Phases tab; the AUC start and base in Settings → Roasting.
-
Audit log (Settings → Audit, Control): every request that changed something, refused ones included, with the credential, the proxy's signed-in user, the browser and the address, the roaster and the body (secrets masked). Kept a year. Migration 022.
-
Security headers on every response (Content-Security-Policy,
X-Frame-Options,nosniff,Referrer-Policy,Permissions-Policy), Security, and CI gates forcargo audit(ignores explained in.cargo/audit.toml) andnpm audit. -
API tokens (Settings → Tokens, Control "Tokens", Security):
rr_…tokens for scripts and agents, stored hashed and shown once, with an expiry and three presets (read-only, roast, admin) checked by method and path, deny by default. Only the owner key manages them. A token changes nothing for a roaster other thanvirtual_roasterunless the owner switches on "Tokens may control this roaster" (Roasters → the roaster → Control). The audit log names the token. Migration 023. -
Alarm notifications and an installable app (Push and the installed app): the dashboard installs as an app (manifest, icons, a service worker that caches only build assets), and every fired alarm is pushed to the devices that turned notifications on (Settings → This browser), with a test button and a device list. Web Push is encrypted and signed in the server (RFC 8291 / 8292, no OpenSSL). Migration 024.
-
Notification channels (Notifications): alarms also go to Gotify (or rstify), ntfy, a Matrix room and the webhook, each switched on, saved and tested on its own card under Settings → Notifications (the test uses the values as typed). Copied from svrctlrs.
-
System events through the same channels, once when a problem starts and once when it clears: the nightly backup failed, a roaster sent nothing outside a roast, a bean fell below its low-stock mark. Open problems survive a restart. Migration 025.
-
Settings → About: dashboard and server versions, revision, schema, uptime, MQTT and database health, last backup, open problems and a link to this changelog at the running revision. Every response carries an
x-request-id. The installed app has Roast, Beans and History shortcuts.
Security
- Secret settings (notification tokens, the webhook URL) are never read back through the
API, only whether they are set; a form resending them empty keeps them. Setting values
are checked on the way in (
PUT /api/settingswrites several, all or none). - The owner key is no longer accepted in a query string, except on the device WebSocket
(
/ws/device/…), where a roaster may not be able to set a header. - Restoring a backup, managing tokens and the per-roaster token switch need the owner key.
Changed
- One JSON error body for every API error (Control, "Errors"):
{"success": false, "error": {"code", "message"}}, with codes such asTOKEN_EXPIREDorREAL_HARDWAREfor refused credentials; an unknown/api/path is a JSON 404 instead of the dashboard's page. - The alarm webhook moved from Settings → Alarms to Settings → Notifications
(
alarm_webhook_urlbecomesnotify_webhook_url); its payload is unchanged. - Stack upgrade (
docs/stack-upgrade.md), step 1: dependencies updated within their lines (tokio 1.54, rustls 0.23.46, anyhow, bytes; svelte 5.57, Kit 2.70, echarts 6.1, Tailwind 4.3, Vite 7.3.7), prometheus 0.14 without protobuf, thiserror 2, hostname 0.4. The unused utoipa and utoipa-swagger-ui crates and the adapter-auto package are gone; the image builds on a pinnedrust:1.99-bookwormwithout OpenSSL; the dev shell is on svrctlrs's nixpkgs (Rust 1.99,cargo audit); CI clippy checks every target and dead code. - Step 2: axum 0.8.9 with one tokio-tungstenite (0.29, axum's), tower-http 0.6.11. A POST whose JSON body is optional still accepts an empty body; a malformed one is now a 400 instead of being ignored.
- Step 3: sqlx 0.8.6 without a TLS feature (clears RUSTSEC-2024-0363 and sqlx's rustls 0.21
chain). Its
RETURNINGrows commit at once now. - Step 4: rumqttc 0.25.1 on rustls 0.23 with ring as the one crypto provider (no aws-lc), tokio-modbus 0.17, mdns-sd 0.21. One rustls in the build.
- Step 5: the dashboard on SvelteKit 3.1, Vite 8, Vitest 5, TypeScript 6, adapter-static 4,
jsdom 30 and
@lucide/svelte(lucide-svelteis deprecated);npm auditreports nothing.
Removed
- The raw telemetry log (
telemetrytable, every payload kept 7 days) andGET /api/roaster/{device_id}/telemetry, which read it: nothing else did, and it was most of the database (379 MB → 8 MB on production). Live views use the 2 h ring; roasts keep their own telemetry.RUSTROAST_DB_RETENTION_SECSis gone. Migration 020.
Fixed
roast bean add --url(the offer's link) collided with the global--url(the server): it is--linknow, and a test fails when a subcommand reuses a global flag's name.- Turning notifications on in the installed app failed with "the app's service worker did
not start": the edge login redirected the browser's fetch of
/service-worker.js. The static app shell now skips the login (Running it, "Access"). roastinside the container used the owner key fromRUSTROAST_API_KEYwhen no token was given, so anything that coulddocker execacted as the owner (an agent in the eval did); it uses only--token/RUSTROAST_TOKEN.- Settings → Notifications was wider than a phone when a channel's last error held a long URL (a Matrix send URL); the error now wraps.
- A bean's stock could read 3199.9998 g after a correction (moves summed as 32-bit floats); stock is whole tenths of a gram.
- Applying a staged restore could fail with "could not move rustroast.db-wal aside" when SQLite removed that file at the same moment.
- Errors from the device configuration API (
/api/devices,/api/device-profiles) came as{"error", "status"}instead of the one error body every other route uses. - Editing a roast event (
PUT /api/sessions/{id}/events/{event_id}) always failed with a 500: it set anupdated_atcolumn the table never had. An unknown event is now a 404 and an empty edit a 400. - The History roast page and the profile page scrolled sideways on a phone.
- The stored AUC ran from the start of recording and through the cooling; it now runs from CHARGE (or the AUC start) to DROP.
- Saving a roast's details after typing a weight failed with "c.trim is not a function" (number inputs hand back numbers), and the first roasted weight entered left weight loss empty until the next save.
- The Roast page's "Autotune finished" banner no longer applies gains itself: it saved the firmware's own ZN-classic gains over a converted gain-schedule row. It links to the Autotune tab, where the rule is chosen.
- The tab icon is the rustRoast flame (it was an amber circle that read as a warning).
- Profile drive no longer lags the profile for the first two minutes after Charge (on the
real roaster 7–11 °C behind, heater 2–40 %): the firmware started its PID from 0 % output,
which loaded an integral of about −96 %. The sequence now starts the drive from a heater
output (
charge_heater_pct, 60 %). Drop and Cancel leave the roaster in Manual at 0 %. The virtual roaster copies the firmware's PID start, so it shows the same lag without it. - Roast page, Control tab: the fan slider and setpoint box follow the roaster when Profile drive, the roast sequence or an alarm changes them (they were read once, on load).
- Roast page, Control tab: the profile-drive note is one line, so the tab fits mid-drive at 1280×720 too; the setpoint box is wide enough for one decimal in the new face.