The clarity backend exposes a Warp HTTP/HTTPS server on port 3030. The server is initialized inside the Tauri setup closure and runs in a Tokio runtime.
Sources:
clarity:backend/src-tauri/src/main.rsclarity:backend/src-tauri/src/auth.rsSource: clarity:backend/src-tauri/src/main.rs:1486-2307
mimalloc (clarity:backend/src-tauri/src/main.rs:4-7)server_port + 10000 (e.g. 13030). bind() is atomic at the kernel level — only one process can hold the port, with zero race window — and the listener is held in _single_instance_lock for the lifetime of main(). If the bind fails, a second instance is running: the process logs to licensing.log and exits. This replaces the old "connect to 3030, exit if open" check and now runs in both debug and release builds. clarity:backend/src-tauri/src/main.rs:1655-1685cert.pem / key.pem in <data>/certs/), auto-generated with SANs localhost + clarity.localclarity.local via the native dns-sd -P proxy; the userspace mdns-sd fallback is skipped when dns-sd succeeds, to avoid duplicate advertisements and noisy errors on interfaces that only have link-local IPv6 (no valid A record). Non-macOS (and macOS when dns-sd fails) uses the mdns-sd daemon. clarity:backend/src-tauri/src/main.rs:2456-2503clarity:backend/src-tauri/src/main.rs:133), now backed by an account lockout — see Login lockoutclarity:backend/src-tauri/src/main.rs:2311-2339)c41a03b): first run 5 min after startup, then every clarity.storage.seal.interval_secs (.max(60), default hourly), calling Storage::seal_old_day_files(hot_days, grace_secs) via spawn_blocking — hot_days default 0 (seal everything before today). clarity:backend/src-tauri/src/main.rs:3575-3600 — see Storage Engine § Cold-tier compressionc41a03b): disk_guard::start(storage_path) (clarity:backend/src-tauri/src/main.rs:3250) spawns a named thread that samples free space every 10 s (clarity:backend/src-tauri/src/disk_guard.rs:26); below clarity.storage.min_free_disk_mb (default 2048, 0 disables) it flips WRITES_BLOCKED and every ingest endpoint (/write, /write_fast, /write_buffered, /blob_write) returns HTTP 507 INSUFFICIENT_STORAGE with blocked_message(); unblocks at 1.25× the threshold (hysteresis). Reads, ring drains, sealing, and metadata are not blocked. As of 45a686e the guard also resolves the data volume correctly on Windows: strip_verbatim removes the \\?\ verbatim prefix (rewriting \\?\UNC\… → \\…) from the canonicalized path before mount-point matching — previously Path::canonicalize returned \\?\C:\…, which never matched a C:\ mount point, so free_space_for returned None and the guard silently never blocked. disk_guard.rs:1-15, 31-123 (strip_verbatim :48-62); checks at main.rs:814,841,902,1327c41a03b): drains the hot ring buffers every clarity.hot_ring.drain_interval_ms (staggered sub-tick, skips when no ring buffers exist); graceful shutdown and seal_now call flush_all_ring_buffers_final(). clarity:backend/src-tauri/src/main.rs:3609-3633,332445a686e): build_tag_map is spawned as a background task and each warp::serve awaits tag_resolver::wait_tag_map_ready() before serving (see Tag Resolver § Lifecycle); the window navigates as soon as Warp is up, with Python services started fire-and-forget. clarity:backend/src-tauri/src/main.rs:3330-3340, 4829, 5102, 5145-5160clarity:backend/src-tauri/src/main.rs:2117-2219)tauri://localhost, https://localhost:3030, https://clarity.local:3030; methods GET/POST/PUT/DELETE/OPTIONS (clarity:backend/src-tauri/src/main.rs:2855-2858)Before tauri::Builder runs, main() builds its own multi-threaded Tokio runtime with thread_stack_size(8 MB) (up from the Tokio default of 2 MB) and installs it as Tauri's async runtime via tauri::async_runtime::set(rt.handle().clone()); the runtime is then std::mem::forget-leaked so it lives for the whole process. It must be set before tauri::Builder so Tauri's internal runtime inherits the larger stack. The warp compression middleware deflates large static files (the JS bundle is ~5–10 MB) on Tokio worker threads, and the default 2 MB stack overflows there; 8 MB adds headroom at negligible memory cost on 64-bit (stacks are demand-paged). clarity:backend/src-tauri/src/main.rs:1853-1868
Platform note: Windows only.
DLL search-order hardening (new in f3f16e6). At the very top of fn main() — before Tauri, logging, or any extern call, so it runs before any DLL is loaded — the process calls SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS) (flag 0x1000). This drops the implicit current-working-directory and the legacy standard-search-path entries from the DLL search order, leaving only the application directory, System32, and any AddDllDirectory paths. It closes a DLL-planting / DLL-hijack vector (CWE-427) while keeping Tauri / WebView2 loading intact (both resolve from the application directory or System32). clarity:backend/src-tauri/src/main.rs:1687-1700
Inside the Tauri setup closure, on Windows the process calls two further Win32 APIs at startup (clarity:backend/src-tauri/src/main.rs:1885-1898):
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX) — suppresses blocking crash/error dialogs so a panic on a headless server does not stop the Task Scheduler watchdog from restarting the process.SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_AWAYMODE_REQUIRED) — prevents the OS from sleeping a Windows 10/11 machine used as a server, which could otherwise suspend the process mid-flush and corrupt a day file.requireAdministrator manifest (new in 45a686e). build.rs now embeds windows/app.manifest (via tauri_build::try_build(… .app_manifest(include_str!("windows/app.manifest")))) in place of Tauri's default asInvoker manifest; it sets <requestedExecutionLevel level="requireAdministrator"> plus Common-Controls v6, DPI-awareness, and long-path support. The app therefore prompts for UAC elevation once at launch and stays elevated for its lifetime — required because HA VIP management (netsh) and firewall bring-up need a fully elevated process. clarity:backend/src-tauri/build.rs:38-51; clarity:backend/src-tauri/windows/app.manifest:6-17, 42.
Runtime Authenticode self-verification. The security model shifted away from kernel-enforced PE /INTEGRITYCHECK (FORCE_INTEGRITY) — Windows 10 2004+ honors that flag only for Microsoft-rooted signatures and blocks third-party (GlobalSign EV) chains at launch (CodeIntegrity 3004). Instead, build.rs emits cargo:rustc-cfg=clarity_signed_build for Windows release/release-max unless CLARITY_SIGNED_BUILD=0, and at startup integrity::verify_self() calls WinVerifyTrust to confirm the running exe's own Authenticode signature (CWE-354) — an unsigned/tampered binary refuses to start unless opted out. Invoked from clarity:backend/src-tauri/src/main.rs:2387; clarity:backend/src-tauri/src/integrity/mod.rs. The embedded SHA-256 asset manifest also widened to hash native runtime libraries (.so/.dll/.pyd/.dylib) in the Python/g-adk/gtk runtime dirs, not just .py/.dat. (WDAC .cip deployment was dropped; ClarityEnginePolicy.xml ships only as an IT hand-off artifact, outside the Rust code.)
Platform note: Windows only.
Every Windows subprocess call now invokes its system binary by absolute path under C:\Windows\System32\ (or …\wbem\ / …\WindowsPowerShell\v1.0\) and bails out with a logged error if the file is missing, instead of relying on PATH resolution. This hardens against PATH-hijack and broken-PATH environments. Affected callers span the codebase: certutil.exe, schtasks.exe, reg.exe (main.rs, persistence.rs), netsh.exe / net.exe / ping.exe (ha/firewall.rs, ha/vip_manager.rs), sc.exe / net.exe / passwd.exe / powershell.exe (mqtt_process.rs, process_manager/service.rs), tasklist.exe / netstat.exe / taskkill.exe (process_manager/supervisor.rs, python_services/service_manager.rs), and wmic.exe / powershell.exe for hardware queries (licensing/fingerprint.rs). clarity:backend/src-tauri/src/main.rs:2065-2083, 4336-4345
main() installs a std::panic::set_hook before any other init so any crash is written to the licensing log even under windows_subsystem="windows" (which swallows stderr). write_licensing_log_direct() now writes ISO-8601-timestamped lines to two files — the primary …/clarity/logs/licensing.log and a fallback clarity_startup.log next to the executable (survives %APPDATA% permission failures). Startup milestones are logged in sequence: pid/args, config loaded, single-instance lock, WebView2 + MSVC-runtime registry/DLL probes, tauri::Builder entry, setup() entry ("WebView2 OK"), and the full license-check result. clarity:backend/src-tauri/src/main.rs:1655-1684, 1686-1710, 1753-1845
On Windows, before Tauri loads, the process probes the registry for the WebView2 Evergreen Runtime (HKLM/HKCU EdgeUpdate\Clients\{F3017226-…}) and checks for vcruntime140.dll / msvcp140.dll; missing either logs a WARNING explaining the window will not appear and which redistributable to install. clarity:backend/src-tauri/src/main.rs:1794-1844
The WriteBuffer is now constructed with a Write-Ahead Log path in all modes (single-node and HA), not only when HA is enabled. The WAL (<data>/write_buffer.wal, split per shard) guards against losing buffered writes in the gap between enqueue and the 20ms background flush. clarity:backend/src-tauri/src/main.rs:2520-2536 — see Storage Engine § Write Buffer.
As of
1e53e3fthe buffer uses 8 shards (was 4), the WAL is written with a compact binary codec (was JSON; legacy JSON files still replay), and the WALfsynchappens once per batch instead of per append — all to lift concurrent-write throughput. Details in Storage Engine § Write Buffer.
Platform note: Windows only.
A spawned task listens for ctrl_shutdown (system reboot), ctrl_logoff, and ctrl_close (console close). On any of these it sleeps 200 ms (≈10 flush cycles) to let the background flushers drain, logs, then process::exit(0). Anything still buffered is covered by the WAL and replayed on next startup. clarity:backend/src-tauri/src/main.rs:2539-2570
After the Python services are wired up, process_manager.start_watchdog() is invoked to begin periodic auto-start health checks. clarity:backend/src-tauri/src/main.rs:3946 — see Process Manager.
The logger is now initialized at the top of the setup closure, before the license check, so licensing logs are captured (the channel buffers until the file-writing consumer task is spawned later, once data_path is known). A new per-target route sends clarity::licensing log lines to licensing.log (100 MB rotation), alongside the existing per-service log files. clarity:backend/src-tauri/src/main.rs:1714-1727, 2390-2441 — see Licensing.
The system-tray "show" menu item and the tray-icon click handler now reveal the main window only if licensing::runtime::verify_license().is_ok() — a bricked/expired install cannot un-hide its window from the tray. clarity:backend/src-tauri/src/main.rs:4052-4076
New in 67ac68c. After the v2 DB is initialized (and before the monitor system starts), main.rs calls sqlite_api::api::sync::sync_folder_to_db(&db, storage_path) once. It walks {storage_path}/<org>/<site>/<unit>/<grid>/metadata.json on disk and ensures every (org, site, unit) tuple that has at least one grid with a metadata.json also has matching rows in the DB (via the idempotent ensure_org/ensure_site/ensure_unit helpers, which link the resource to admin). This recovers DB hierarchy rows for collections that exist on disk but were never POSTed through the API — chiefly backup restores. The result counts (triples, ensure_org_calls, ensure_site_calls) are logged; failures per tuple are logged and skipped. clarity:backend/src-tauri/src/main.rs:4121-4133 — see SQLite API § Folder↔DB sync.
auth.rsSource: clarity:backend/src-tauri/src/auth.rs
hash_password uses Argon2 (default params) with a random 16-byte salt (OsRng).
clarity:backend/src-tauri/src/auth.rs:57-68
Log-redaction fix.
verify_passwordno longer logs the stored-hash prefix on a failed verification (the Argon2 hash prefix embeds the salt). Failed-login tracking / IP logging is now the caller's responsibility (feeding the new per-IP + progressive-delay limits).clarity:backend/src-tauri/src/auth.rs:140-147
create_jwt encodes with HS256. The JWT_SECRET is resolved once at startup (lazy_static): the runtime JWT_SECRET env var wins only if set and non-empty, otherwise it falls back to the value baked in at compile time via env!("JWT_SECRET", …) (sourced from build.sh). An empty env var no longer shadows the embedded secret. clarity:backend/src-tauri/src/auth.rs:31-40
Token expiry: default 86400 seconds = 24 hours from issue time. A JWT_EXPIRY_MINUTES env var takes precedence over the config value when set and parseable (minutes × 60); otherwise the expiry is clarity.auth.jwt_expiry_seconds from clarity.properties (default 86400). clarity:backend/src-tauri/src/auth.rs:108-123
Service accounts use exp=0 / token_never_expires=true for non-expiring tokens.
Conflict note:
SECURITY.mdstates "1-hour expiry". The source code default is86400(24h). The code is authoritative;SECURITY.mdis outdated.
Claims struct (clarity:backend/src-tauri/src/auth.rs:36-49):
| Field | Type | Description |
|---|---|---|
sub |
String |
User email (subject) |
exp |
usize |
Unix expiration timestamp (0 = never expires) |
role |
String |
"admin" | "read-write" | "read-only" |
units_id |
Vec<i64> |
Accessible unit IDs (populated from userprofiles) |
sites_id |
Vec<i64> |
Accessible site IDs |
orgs_id |
Vec<i64> |
Accessible org IDs |
token_never_expires |
bool |
If true, expiry check is bypassed |
Two-pass strategy: first verify with validate_exp=true, then retry with validate_exp=false if first fails (accepts exp=0 or token_never_expires=true tokens).
clarity:backend/src-tauri/src/auth.rs:119-161
Case-insensitive
Bearerprefix (as of9847dba).verify_jwt/verify_jwt_blockingnow strip either"Bearer "or"bearer "(viastrip_prefix) after trimming, and use the raw token if neither prefix is present.auth_middlewarewas simplified to only reject a fully empty token (it no longer strips the prefix itself — that is nowverify_jwt's job), so abearer …header no longer fails auth. The "token expired" log line was also downgradedwarn!→debug!(expected for stale internal tokens).clarity:backend/src-tauri/src/auth.rs:157-159, 203-205, 552-560
auth_middleware(token): verifies JWT, returns Claims. Used by most routes.admin_middleware(token, db): verifies JWT then checks role=="admin" from SQLite.clarity:backend/src-tauri/src/auth.rs:471-499
Three roles exist in the system. The role field is stored in user meta_data JSON.
| Role | Source |
|---|---|
admin |
Full access: CRUD, user management, system config, backup |
read-write |
Standard user; assigned by POST /exactapi/register |
read-only |
Fallback default if no role in user metadata (clarity:backend/src-tauri/src/auth.rs:325) |
Conflict note:
SECURITY.mddocuments only two roles (admin,read_write). The source code has three:admin,read-write,read-only.read-onlyis the default for users whose metadata lacks a role field.
Two users are seeded from clarity:backend/src-tauri/Assets/seed_data/users.json when the users table is empty at DB initialization.
| Password source | Role | Notes | |
|---|---|---|---|
admin |
env ADMIN_PASSWORD → secret pack |
admin |
tokenNeverExpires: false |
opcuser |
env OPC_PASSWORD → secret pack |
admin |
tokenNeverExpires: true; authorizedSystems: ["client","ingestconfigs","tags","ingest"] |
Plaintext passwords removed from the seed data (as of
a18d35c).users.jsonno longer containspasswordfields, and the standaloneopcuser.jsonseed file was deleted. Passwords now come from the secret pack: the seeder readsADMIN_PASSWORD/OPC_PASSWORDfrom the pack, hashes with Argon2, and stores themeta_datablob without any password field. A user not matchingadmin/opcuseris seeded with an empty password and a warning.clarity:backend/src-tauri/src/sqlite_api/db/migrations.rs:800-830
ensure_admin_user is a separate startup fallback in main.rs that creates a default admin if no admin row exists; it likewise provisions and reads ADMIN_PASSWORD from the secret pack rather than hardcoding a password. clarity:backend/src-tauri/src/main.rs:1483-1514
check_auth_rate_limit(email) runs before every login. A process-wide ACCOUNT_LOCKOUTS: DashMap<String, Instant> records a lockout expiry per email. The check is two-stage:
AccountLocked { retry_after_secs } → HTTP 429 with body {"error", "code":"ACCOUNT_LOCKED", "retryAfter"}. Expired entries are removed on access.AUTH_RATE_LIMITER). If exceeded, the account is locked for auth_lockout_seconds (config clarity.auth.lockout_seconds, default 900 s / 15 min) and the request is rejected.clarity:backend/src-tauri/src/main.rs:139-202, 4100-4113, clarity:backend/src-tauri/src/config.rs:169,266,432-434
Applies to: all modes. See also SQLite API § secret-pack seeding and the Security model.
Changed in
1027dce— the OS keychain was removed.secure_store.rsno longer uses thekeyringcrate (which is gone fromCargo.toml). All seed-data secrets now live in a single AES-256-GCM + HMAC-SHA256 encrypted file on disk, mirroring the file-only licensing keystore:~/.clarity/secrets/secrets-pack.bin(Linux/macOS,0o600) or%APPDATA%\clarity\secrets\secrets-pack.bin(Windows, NTFS-restricted to the user).clarity:backend/src-tauri/src/secure_store.rs:1-45
File format (identical layout to licensing/keystore.rs): [32-byte HMAC-SHA256 of ciphertext] [12-byte AES-GCM nonce] [ciphertext + 16-byte GCM auth tag]. Writes are atomic (temp file + rename). clarity:backend/src-tauri/src/secure_store.rs:18-22, 157-194, 240-260
Key derivation. AES and HMAC keys are derived via blake3::derive_key(ctx, material) where material = hardware_fingerprint ‖ JWT_SECRET (the build-baked JWT secret). The file therefore decrypts only on the same machine AND with the same binary — copying secrets-pack.bin elsewhere yields gibberish; HMAC failure surfaces as "tampered or wrong machine". Same threat model as the license keystore. clarity:backend/src-tauri/src/secure_store.rs:26-34, 137-153
At DB-migration time, provision_pack() runs on every startup (even existing installs) to keep the encrypted pack in sync with the environment. For each (env_var, key, fallback) it takes the env var when set and non-empty, else preserves the existing on-disk value, else the fallback. Four secrets are provisioned: ADMIN_PASSWORD, OPC_PASSWORD, MQTT_BROKER_USERNAME (fallback admin), MQTT_BROKER_PASSWORD. clarity:backend/src-tauri/src/sqlite_api/db/mod.rs:300-313, clarity:backend/src-tauri/src/secure_store.rs:280-321
keys:: constant |
Pack key | Seeded from env var |
|---|---|---|
ADMIN_PASSWORD |
admin-password |
ADMIN_PASSWORD |
OPC_PASSWORD |
opc-password |
OPC_PASSWORD |
MQTT_USERNAME |
mqtt-broker-username |
MQTT_BROKER_USERNAME |
MQTT_PASSWORD |
mqtt-broker-password |
MQTT_BROKER_PASSWORD |
Build-time
ADMIN_PASSWORDfallback (new in1027dce). TheADMIN_PASSWORDpair's fallback is no longer empty — it isoption_env!("ADMIN_PASSWORD"), the value ofADMIN_PASSWORDat compile time. So the effective admin-password priority is: runtime env var → existing on-disk pack value → build-time baked value → empty. A distributor can bake a default admin password into the binary;build.rsdeclarescargo:rerun-if-env-changed=ADMIN_PASSWORDso changing it triggers a rebuild.clarity:backend/src-tauri/src/sqlite_api/db/mod.rs:309,clarity:backend/src-tauri/build.rs:82
These pack values are consumed by user seeding (db/mod.rs), MQTT config seeding (broker creds injected at runtime, not compiled into configs.json), the PI driver (process_manager reads ADMIN_EMAIL + ADMIN_PASSWORD instead of hardcoded admin/admin123), and ensure_admin_user. clarity:backend/src-tauri/src/process_manager/supervisor.rs:357-366,416-424
A legacy single-key API (store_secret/load_secret/has_secret/delete_secret) is retained for one-off secrets; each value is its own <key>.bin encrypted file in the same directory. clarity:backend/src-tauri/src/secure_store.rs:323-369
| Variable | Type | Required | Description |
|---|---|---|---|
JWT_SECRET |
Build-time, runtime override | Required at build | Signing secret for HS256 JWT. Baked in at compile time via env!("JWT_SECRET", …); a non-empty runtime JWT_SECRET overrides it. No plaintext default and no runtime startup panic (a release binary built with the secret needs no env var at launch). clarity:backend/src-tauri/src/auth.rs:31-40 |
JWT_EXPIRY_MINUTES |
Runtime | Optional | Overrides token lifetime (minutes × 60) when set and parseable; otherwise clarity.auth.jwt_expiry_seconds (default 86400). clarity:backend/src-tauri/src/auth.rs:108-123 |
DB_PASSWORD |
Runtime / build-time | Required | SQLCipher encryption key for pulse-db.sqlite. |
ADMIN_EMAIL, ADMIN_PASSWORD |
Runtime (+ build-time fallback for ADMIN_PASSWORD) |
Optional | Seed admin identity; provisioned into the encrypted secret pack at startup. ADMIN_PASSWORD also has a option_env! build-time baked fallback. |
OPC_EMAIL, OPC_PASSWORD |
Runtime | Optional | Seed opcuser credentials → secret pack. |
MQTT_BROKER_USERNAME, MQTT_BROKER_PASSWORD |
Runtime | Optional | Mosquitto broker credentials → secret pack; injected into configs seed at runtime. |
Dev/build env vars are listed in
clarity:backend/src-tauri/env_vars.sh. The seed-credential and MQTT-broker vars are read at runtime and persisted into the encrypted secrets-pack file (~/.clarity/secrets/secrets-pack.bin), not the OS keychain. OnlyADMIN_PASSWORDmay additionally be baked into the binary as a build-time fallback.
Conflict note: Old
API_REFERENCE.mdstatesJWT_SECRETdefaults to"abc"if not set. There is no such default — the secret is embedded at build time. The old documentation is inaccurate.
/exactapi/auth/refresh — NOT IMPLEMENTEDThe old API_REFERENCE.md documents a POST /exactapi/auth/refresh endpoint. This route does not exist in main.rs. No refresh token logic is present in auth.rs. Do not document as available.
At login, get_user_hierarchy_access reads the userprofiles table (keyed by json_extract(meta_data, '$.email')) and the units table to populate units_id, sites_id, orgs_id JWT claims from the user's accessAllowed array.
clarity:backend/src-tauri/src/auth.rs:401-505
meta_data tolerance: the userprofiles.meta_data read tolerates Blob (canonical), Text (legacy), and Null storage; any other column type returns an error. A missing accessAllowed array, a missing profile, or a lookup error each log a warning/error and resolve to empty (units, sites, orgs). clarity:backend/src-tauri/src/auth.rs:410-420, 435-438, 496-503login_user now matches on get_user_hierarchy_access explicitly — if the lookup errors, it logs and still issues a JWT with empty units_id/sites_id/orgs_id rather than failing the login. clarity:backend/src-tauri/src/auth.rs:349-35967ac68c): the accessAllowed entries' sitesId and units[] values are now read via a shared parse_id(Option<&Value>) helper that accepts either a JSON number or a numeric string ("21"). The API serializes integer IDs as strings in responses (see SQLite API § ID fields stringified), and those responses are sometimes round-tripped back into other rows' meta_data BLOBs, so the hierarchy reader must accept both forms. clarity:backend/src-tauri/src/auth.rs:93-100, 459-471NOTE: An empty
units_idis not harmless for non-admins — under the row-level access policy a non-admin with emptyunits_idis denied all row access. A hierarchy-lookup failure therefore yields a usable token that can authenticate but reads no scoped rows (admins are unaffected; they bypass the check).
The per-row authorization check (formerly inline in the SQLite CRUD get_item handler) was extracted into auth.rs so the typed and dynamic CRUD layers share one implementation with identical semantics. clarity:backend/src-tauri/src/auth.rs:566-740
AccessDenied enum — NoUnitAccess or RowDenied { row_id, units_id } (with user-facing Display).check_row_access(table, row, claims) — pure/sync. Admin bypasses all checks; a non-admin with empty units_id is always denied; for the units table the row's own id must be in claims.units_id; for any other table the row's unitsId must be in claims.units_id (missing / null / unparseable unitsId → deny). As of 67ac68c the row's id and unitsId are read via the same parse_id helper, so a stringified id ("5") materialized from a response or a meta_data BLOB authorizes identically to an integer. clarity:backend/src-tauri/src/auth.rs:681-684check_row_access_with_db(db, table, row, claims) — async variant that resolves unitsId via FK traversal for tables lacking a direct column (dashboardplots → dashboardId → dashboards.unitsId), then applies the same rule.Each caller maps AccessDenied to its framework's error (get_item returns 403 FORBIDDEN with the Display string). clarity:backend/src-tauri/src/sqlite_api/api/warp_dynamic_routes/reply.rs:298-314. See SQLite API § Auth.
New in 9847dba. Request-body JSON is now HTML-sanitized before it is deserialized/stored, to neutralize stored/reflected XSS. Two warp filters from sqlite_api::api::common replace warp::body::json across the write surface:
sanitized_json::<T>() — parses the body as serde_json::Value, recursively HTML-encodes string values that contain an HTML tag, then deserializes into T (returns 400 with a descriptive message if the sanitized value no longer matches T).sanitized_json_value() — same sanitization, returns the raw Value.In main.rs the following hand-written routes were switched to the sanitizing filters: register, login, admin/user/delete, update_password, create_collection, update_collection, fast_query_tag_mapping, write_tag_mapping, create_qdrant_collection, plus a CollectionRequest-typed variant on create_collection. clarity:backend/src-tauri/src/main.rs:3229-3701
The entire SQLite CRUD surface (typed macros, dynamic routes, connections, tag_mappings, collections, attachments' sibling routes) was switched too — see SQLite API § Input sanitization for the encoder details and the bulk-body behavior change.
All route registrations verified from clarity:backend/src-tauri/src/main.rs.
Routes are composed in two groups:
api — all routes wrapped under /exactapi prefix (clarity:backend/src-tauri/src/main.rs:3058-3066)api wrapper (clarity:backend/src-tauri/src/main.rs:3132-3144)/exactapi)| Method | Path | Auth | Source lines |
|---|---|---|---|
| POST | /exactapi/register |
None | main.rs:2344-2364 |
| POST | /exactapi/login |
None (rate-limited) | main.rs:2366-2386 |
| POST | /exactapi/admin/user/delete |
Admin JWT | main.rs:2388-2423 |
| POST | /exactapi/update_password |
Admin JWT | main.rs:2425-2461 |
| GET | /exactapi/me |
JWT | main.rs:2463-2472 |
| GET | /exactapi/me/details |
JWT | main.rs:2474-2492 |
/exactapi/me returns {"user_id": claims.sub, "role": claims.role}.
/exactapi/me/details returns {"unitConfigured": bool} based on whether metadata.json exists for the user's context.
Note:
POST /exactapi/admin/user/addandGET /exactapi/admin/usersfrom old API_REFERENCE are NOT found as distinct routes inmain.rs. User listing/creation is handled by the SQLite CRUD API (/exactapi/v2/userprofiles).
/exactapi)| Method | Path | Auth | Source lines |
|---|---|---|---|
| POST | /exactapi/create_collection |
JWT | main.rs:2494-2514 |
| POST | /exactapi/update_collection |
JWT | main.rs:2517-2548 |
| GET | /exactapi/collection |
JWT | main.rs:2868-2917 |
| GET | /exactapi/collection/{id} |
JWT | main.rs:2919-2976 |
/exactapi)| Method | Path | Auth | Description | Source lines |
|---|---|---|---|---|
| POST | /exactapi/write |
JWT | Standard write, fsync | main.rs:2605-2618 |
| POST | /exactapi/write_fast |
JWT | mmap write, no fsync, <100ms target | main.rs:2622-2634 |
| POST | /exactapi/write_buffered |
JWT | Queued write, 50ms flush (config-driven as of c41a03b), <1ms target |
main.rs:2639-2651 |
| GET | /exactapi/write_buffer_stats |
JWT | Queue counters and error stats | main.rs:2655-2670 |
| POST | /exactapi/shadow_write |
JWT | HA zero-loss failover — a Secondary buffers the raw write body for gap-fill on promotion; no-op on the Leader / when disabled. Leader-ungated (mounted in read_routes). See HA § Secondary Shadow Cache. |
main.rs:4064-4077, handler main.rs:1344-1354 |
57c553a; restructured in c41a03b)When a write omits org/site/unit/grid, each tag's scope is looked up from TAG_SCOPE_MAP (see Tag Resolver). The write path handles a stale map — e.g. a tag whose tagmeta was uploaded after the last map build:
As of
c41a03b,parse_write_request(andfind_binary_path) are removed; the write path is factored intoresolve_write_scopes(clarity:backend/src-tauri/src/main.rs:177),build_scoped_writes(:203), andrun_write(:400) with the same resolve/refresh-and-retry/skipped-tags semantics described below.
handle_write (/write) and handle_write_mmap (/write_fast) clone the request body, and if the first parse reports any unresolved tags (or errors with "No tags could be resolved"), they call build_tag_map(storage) once to rebuild TAG_SCOPE_MAP and re-parse. If the retry still fails, they fall back to the partial result from the first parse. clarity:backend/src-tauri/src/main.rs:836-911, 913-973handle_write_buffered (/write_buffered) does the same one-shot rebuild in its single, bulk, and batch branches when any tag is unresolved. To do so the route now also injects Storage into the handler. clarity:backend/src-tauri/src/main.rs:979-1027, 1120-1150, 1226-1256/write and /write_fast append " (skipped N unresolved tag(s): …)" to the ✅ message listing tags that could not be resolved even after the refresh. clarity:backend/src-tauri/src/main.rs:896-906, 1071-1090execute_write returns an error ("No writable data: all tags were unresolved or all batches were empty") when scope resolution yields no writable batches, instead of a vacuous ✅ Write successful. clarity:backend/src-tauri/src/main.rs:750-760/write_fast. handle_write_mmap now returns 400 Bad Request on a parse failure and 500 Internal Server Error on a write failure (was always 200 with a ❌-prefixed body), so SDK callers using raise_for_status() see the failure. clarity:backend/src-tauri/src/main.rs:913-978c41a03b)String/array values via the blob store. All routes JWT-authenticated (auth_middleware); blob_write is disk-guard-gated (507 when blocked):
| Method | Path | Description | Registration / handler |
|---|---|---|---|
| POST | /exactapi/blob_write |
Write string/array tag values | main.rs:4134-4142 / handle_blob_write main.rs:896 |
| GET/POST | /exactapi/blob_query |
Range read of blob values | main.rs:4145-4163 / main.rs:1034 |
| GET/POST | /exactapi/blob_lastlist |
Latest blob value per tag | main.rs:4166-4184 / main.rs:1092 |
| POST | /exactapi/blob_search |
Predicate search (Eq/Contains/Regex) | main.rs:4187-4195 / main.rs:1143 |
| POST | /exactapi/blob_state_durations |
State-duration aggregation over blob values | main.rs:4198-4208 / main.rs:1222 |
| POST | /exactapi/seal_now |
Force cold-tier sealing (optional scope, optionally incl. today) | main.rs:3994-4018 |
c41a03b)config.rs (+244) added keys for the new subsystems — sealing (clarity.storage.seal.*), disk guard (clarity.storage.min_free_disk_mb), blob limits (clarity.blob.*), write-buffer sizing/backpressure/WAL (clarity.write_buffer.*), hot ring (clarity.hot_ring.*), ingest write-method + MQTT gate (clarity.ingest.write.method, clarity.ingest.mqtt.publish), and a relocatable data dir (clarity.data.dir / env CLARITY_DATA_DIR via resolved_data_dir_override()). Defaults and effects are tabulated in Capacity Planning § Configuration knobs.
As of 45a686e, config.rs further adds the HA shadow-cache keys (clarity.ha.shadow_cache.enabled|window_seconds|max_entries|max_mb, clarity.ha.shadow_targets) and clarity.ha.reconcile_days (default 14) — see HA § Secondary Shadow Cache and HA § Sealed File Reconciliation. clarity:backend/src-tauri/src/config.rs:340-355, 484-489, 758-771. The default business-unit list stamped into auto-created metadata is now overridable via the CLARITY_BU env var (default ["ems"]) — see SQLite API.
Changed code defaults: storage_metadata_cache_max_entries 256→1024, storage_metadata_cache_ttl_seconds 120→3600 — but the shipped clarity.properties still pins the old 256/120 (the file wins when present). Properties also sets clarity.hot_ring.drain_interval_ms=60000, clarity.ingest.write.method=fast (code default buffered — so the shipped /ingest path now takes the fast/mmap write), and clarity.ingest.mqtt.publish=OFF against code defaults 5000/buffered/true, and clarity.monitor.agent.poll_interval_ms 100→1000.
Properties key renames (file fixed 2026-07-05): clarity.server.cors_allowed_origins→clarity.cors.allowed_origins; clarity.user_api.rate_limit.*→clarity.rate_limit.user_api.* (value 2000→20000); ingest rate keys →clarity.rate_limit.ingest.max_requests/.window_seconds; clarity.ingest.config_cache_ttl_seconds→clarity.storage.config_cache.ttl_seconds; clarity.ingest.http.*→clarity.ingest.http_max_idle_per_host/http_client_timeout_seconds; clarity.ingest.token.*→clarity.auth.internal_token_ttl_seconds=3600/internal_token_refresh_before_seconds=300; new clarity.auth.lockout_seconds=900.
notification-engine + cloudsync + staging)config.rs (+373) added several new sections (clarity:backend/src-tauri/src/config.rs:279-501):
clarity.storage.staging.*): enabled (code default false, properties ships true), fsync (true), materialize_slots (1024), materialize_max_age_secs (1800), max_pending_bytes (1 GiB), tick_secs (30). See Storage Engine § Staging. clarity:backend/src-tauri/src/config.rs:279-298clarity:backend/src-tauri/src/config.rs:299-311): the login limiter default dropped 30 → 10 and is env-overridable (CLARITY_LOGIN_MAX_REQUESTS); new per-IP limiter (auth_ip_rate_limit_max_requests=20 / _window_seconds=900 / auth_ip_lockout_seconds=3600) and progressive delay (auth_progressive_delay_start=5 / _max_seconds=30). Also attachment_rate_limit_* + attachment_max_file_size_bytes (100 MB). ⚠️ Note the code/properties mismatch: clarity.properties still pins the login limit to 30, so the effective limit on a shipped install is 30; the new auth_ip_* / progressive-delay keys are absent from clarity.properties and run on code defaults only. clarity:backend/src-tauri/clarity.properties:181clarity.notifications.*): retention_days (30; 0 disables purge, unread kept), retention_interval_seconds (86400). clarity:backend/src-tauri/src/config.rs:449-455clarity.smtp.* / clarity.mail.*): SMTP host/port/creds/encryption + mail-queue retry/backoff, attachment limits, and a per-minute rate limit. Detail in Mail / SMTP § Configuration. clarity:backend/src-tauri/src/config.rs:457-501clarity.cloudsync.*): source/remote URLs + credentials, lookback_minutes, backfill window, tag/collection filters, batch size. Detail in CloudSync § Config. clarity:backend/src-tauri/src/config.rs:397-448query_cache_max_entries / query_cache_ttl_seconds.c41a03b changesapi/start_adk.rs deleted (−371): a dead run_binary_in_thread helper never referenced outside its own module declaration; the live ADK surface (api/google_adk.rs, adk_routes) is unchanged (main.rs:76,4564,4639).win_console.rs (new): HideConsole trait sets CREATE_NO_WINDOW on std/tokio Commands — the release build is GUI-subsystem, so console children (reg/netsh/taskkill/python…) would otherwise flash windows; adopted across mqtt, python services, HA, process manager, licensing. clarity:backend/src-tauri/src/win_console.rsClarityEngineWatchdog scheduled task runs wscript.exe //B clarity_watchdog.vbs (WMI process query + relaunch) instead of a PowerShell command — wscript is GUI-subsystem, so the periodic task no longer flashes a conhost window. clarity:backend/src-tauri/src/persistence.rsasset_body(Cow<'static,[u8]>) serves embedded assets without a per-request heap copy. clarity:backend/src-tauri/src/static_assets/handler.rs:16-21installMode currentUser → perMachine (tauri.conf.json:40); the installer ships clarity.properties.default and copies it to $INSTDIR\clarity.properties only if none exists (upgrades never clobber site edits). Windows release binaries and every PE under Assets\ are now Authenticode-signed (windows-sign.conf.json, sign-assets.ps1, verify-signing.ps1; SHA-256, GlobalSign TSA).auth.rs perf: JWT decoding key/validation objects and the secret fingerprint are cached in OnceLock statics; user queries use prepare_cached. No route/RBAC/token-semantics change. clarity:backend/src-tauri/src/auth.rs:48-82/exactapi)| Method | Path | Auth | Description | Source lines |
|---|---|---|---|---|
| GET | /exactapi/fast_query |
JWT | JSON response, optional pipeline | main.rs:2684-2692 |
| POST | /exactapi/fast_query |
JWT | JSON response, optional pipeline | main.rs:2694-2702 |
| GET | /exactapi/fast_query_binary |
JWT | Binary format v1 (per-tag timestamps) | main.rs:2719-2727 |
| POST | /exactapi/fast_query_binary |
JWT | Binary format v1 | main.rs:2730-2738 |
| GET | /exactapi/fast_query_optimised |
JWT | Binary format v2 (shared timestamps + bitmaps) | main.rs:2741-2749 |
| POST | /exactapi/fast_query_optimised |
JWT | Binary format v2 | main.rs:2752-2760 |
| GET | /exactapi/lastlist |
JWT | Latest data point per tag | main.rs:2785-2793 |
| POST | /exactapi/lastlist |
JWT | Latest data point per tag | main.rs:2796-2804 |
| GET | /exactapi/context/options |
None | Org/site/unit hierarchy stub | main.rs:2844-2849 |
/exactapi)| Method | Path | Auth | Description | Source lines |
|---|---|---|---|---|
| POST | /exactapi/tag_mappings |
JWT | Register or retrieve tag spec → generatedDataTagId |
main.rs:1106-1323 |
| POST | /exactapi/fast_query_tag_mapping |
JWT | Query using tag spec objects | main.rs:2763-2771 |
| POST | /exactapi/write_tag_mapping |
JWT | Write using tag spec objects; auto-creates collection | main.rs:2774-2782 |
Full tag_mapping CRUD (GET list, GET by ID, etc.) is served by the SQLite CRUD API via the
register_models!macro.
/exactapi)CRUD route registration in warp_routes.rs. All entity paths follow /exactapi/{entity} directly — there is no /v2 sub-prefix in the actual HTTP paths (the internal variable is named _api_v2_crud_routes but the warp path is warp::path("exactapi")).
clarity:backend/src-tauri/src/sqlite_api/api/warp_routes.rs
clarity:backend/src-tauri/src/main.rs:3749-3754
As of
e021b09, these URLs are served by the dynamic relation-discovery layer rather than the typed macros. Every table is listed inDYNAMIC_TABLES, so the typedcrud_routes!chain is gated out (via a__dyn_skip__sentinel) and requests fall through to the runtime-discovered dynamic Warp routes mounted last. The typed macros remain compiled as the contract reference. AGET /exactapi/dyn/_schema(admin) endpoint dumps the discovered relation graph. See SQLite API § Dynamic Relation-Discovery Layer.
Registered entity types (verified from clarity:backend/src-tauri/src/sqlite_api/schema.rs):
units, equipment, tagmeta, deviations, faulttrees, faulttemplates, incidents, calculations, dashboards, dashboardplots, sites, orgs, userprofiles, useractivities, clients, ingestconfigs, statuses, tags, activities, heatrates, modelpipelines, boilerassets, configs, configurations, profiles_lookups, labels, connections, users
Each entity supports: GET (list + filter), POST (create), GET /{id}, PUT /{id}, DELETE /{id}, POST /bulk, POST /alter, GET /count, GET /findOne, GET /{id}/exists, POST /update, PATCH (upsert), DELETE (bulk).
Usersalias: the user CRUD routes are registered under both/exactapi/usersand/exactapi/Users(capitalised) for client compatibility.clarity:backend/src-tauri/src/sqlite_api/api/warp_dynamic_routes/mod.rs:111
configurationsuses a TEXT primary key (MongoDB-style 24-char hex id) rather than the integer auto-increment used by every other entity, so its routes take String ids. See SQLite API.
See SQLite API for the full nested route table (36 verified paths) and LoopBack filter syntax.
/exactapi)Served by sqlite_api/api/warp_attachments. Initialized at startup with default containers.
clarity:backend/src-tauri/src/main.rs:3276-3282
Attachment routes are registered directly under warp::path("exactapi") alongside the CRUD routes (not under a /v2 sub-prefix).
clarity:backend/src-tauri/src/sqlite_api/api/warp_attachments.rs:439-530
| Method | Path | Description |
|---|---|---|
| GET | /exactapi/attachments |
List all containers |
| POST | /exactapi/attachments |
Create a container |
| GET | /exactapi/attachments/:container |
Get container details |
| DELETE | /exactapi/attachments/:container |
Delete a container |
| GET | /exactapi/attachments/:container/files |
List files in container |
| GET | /exactapi/attachments/:container/files/:file |
Get file metadata |
| DELETE | /exactapi/attachments/:container/files/:file |
Delete a file |
| GET | /exactapi/attachments/:container/download/:file |
Download file (streaming) |
| POST | /exactapi/attachments/:container/upload |
Upload file (multipart or raw body) |
Default containers created at startup: tasks, incidents, uploads, mail, pulselogo.
clarity:backend/src-tauri/src/sqlite_api/api/warp_attachments.rs:23-35
9847dba)POST /exactapi/{container}/upload (ct_str/x-filename/content-disposition headers + raw body) had its multipart handling reworked to fix uploads that previously failed with a generic "No files uploaded" 400:
extract_boundary_from_content_type splits the Content-Type on ;, finds the boundary= parameter, and strips surrounding quotes, so quoted boundaries (boundary="----x") and trailing parameters (boundary=----x; charset=utf-8, or charset before boundary) now parse. The old split("boundary=").nth(1) could not.parse_multipart returns Err on a missing boundary (was Ok(vec![]), which surfaced as the generic "No files uploaded") — the error now describes the mismatch.?filename= query → X-Filename header → Content-Disposition header (filename="…", incl. RFC 5987 filename*=utf-8''…) → auto-generated upload_{unix_secs}{ext} where ext is guessed from the content type (guess_extension: pdf/json/csv/xml/html/jpg/png/txt, else .bin).clarity:backend/src-tauri/src/sqlite_api/api/warp_attachments.rs:291-420, 579-620
/exactapi)| Method | Path | Auth | Description | Source lines |
|---|---|---|---|---|
| POST | /exactapi/create_qdrant_collection |
JWT | Forwards to ADK binary at https://localhost:8000 |
main.rs:2807-2814 |
| GET | /exactapi/mdns |
None | Returns {"url": "https://clarity.local:3030"} |
main.rs:2818-2820 |
| GET | /exactapi/ca-cert |
None | Downloads self-signed CA cert as PEM | main.rs:2825-2840 |
| GET | /exactapi/openapi.yaml |
None | Serves embedded OpenAPI spec | main.rs:3035-3044 |
/exactapi)Routes from pi_meta_routes_full registered at /exactapi/pi/*.
clarity:backend/src-tauri/src/main.rs:3063
See wiki/dev/architecture/pi-connector.md for the full PI endpoint table.
/exactapi/monitor)Registered at warp::path("exactapi").and(warp::path("monitor")).
clarity:backend/src-tauri/src/main.rs:3255-3262
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /exactapi/monitor/alarm/snapshot |
None (no JWT filter in this group) | Full alarm snapshot for all collections |
| GET | /exactapi/monitor/alarm/summary |
None | Aggregate triggered count; ?collection_id= filter |
| GET | /exactapi/monitor/alarm/triggered-only |
None | Only collections with triggered tags |
| GET | /exactapi/monitor/alarm/events/active |
None | Currently active (OPEN) alarm events |
| GET | /exactapi/monitor/alarm/events/stats |
None | Event stats; ?from_ms=&to_ms= |
| GET | /exactapi/monitor/alarm/events/{id} |
None | Single event by ID |
| GET | /exactapi/monitor/alarm/events |
None | Paginated event query with filters |
| DELETE | /exactapi/monitor/alarm/events/cleanup |
None | Delete events older than older_than_ms |
| POST | /exactapi/monitor/rule |
None | Create alarm rule |
| GET | /exactapi/monitor/rule |
None | List rules; ?collection_id=&tag_index= |
| GET | /exactapi/monitor/rule/{id} |
None | Get rule by ID |
| PUT | /exactapi/monitor/rule/{id} |
None | Update rule |
| DELETE | /exactapi/monitor/rule/{id} |
None | Delete rule |
| GET | /exactapi/monitor/config |
None | Get monitoring enabled state |
| PUT | /exactapi/monitor/config |
None | Set monitoring enabled state |
| GET | /exactapi/monitor/collections |
None | List monitored collections |
| PUT | /exactapi/monitor/collections/{id}/enable |
None | Enable collection monitoring; optional tick_rate_ms |
| PUT | /exactapi/monitor/collections/{id}/tick-rate |
None | Set collection tick rate |
| PUT | /exactapi/monitor/collections/{id}/disable |
None | Disable collection monitoring |
| GET | /exactapi/monitor/status |
None | System status: enabled, rule count, last snapshot |
Source: clarity:backend/src-tauri/src/monitor/api.rs:135-645
Note: Monitor routes are wrapped with
allow_any_originCORS (distinct from theexactapigroup which restricts origins). No JWT middleware is applied at the Warp filter level in this route group.
Non-fatal monitor init (as of
9847dba). Monitor initialization no longer aborts the whole server-setup task on failure. The DB open, SQLCipher keying, and schema init are wrapped in a fallible closure; on any error it logs and returnsNoneinstead ofreturn-ing out ofsetup(which previously took the warp server down with it). When init succeeds the real/exactapi/monitor/*routes are mounted and — HA-gated — the background monitoring agent is spawned only on the Leader. When init fails, a fallback filter is mounted at/exactapi/monitor/*that answers every request with HTTP 501{"error": "monitoring unavailable — failed to initialize"}, so the rest of the API stays up. Both branches are.boxed()to the same reply type.clarity:backend/src-tauri/src/main.rs:4169-4267
Registered outside api wrapper via adk_routes().
clarity:backend/src-tauri/src/api/google_adk.rs
clarity:backend/src-tauri/src/main.rs:3083, 3140
See wiki/dev/architecture/agents.md for ADK endpoint details.
| Method | Path | Auth | Description | Source lines |
|---|---|---|---|---|
| POST | /sensordata/{id}/lastlist |
JWT | PI-compatible latest-value endpoint; id param is unused |
main.rs:3107-3115 |
| POST | /sensordata/shadow |
JWT | Last-data-timestamp per unit | main.rs:3864-3876 |
Request body: {"query": {"vars": ["tag1", "tag2", ...]}}.
Response: {"data": [{"tag": "...", "cached": "true", "data": [[ts_ms, val]]}]}.
POST /sensordata/shadow — body is ["1-shadow", "2-shadow", …] where the numeric prefix is a SQLite unit ID. handle_shadow resolves each unit ID to (org, site, unit) via resolve_units_path_batch, then fans out one spawn_blocking task per unit calling QueryEngine::get_unit_last_timestamp (see Storage Engine). The response maps each input key to its last data timestamp (ms, as f64); units with no data are omitted. clarity:backend/src-tauri/src/main.rs:4886-4940
Registered by crate::processing_api::elog::elog_routes(db).
clarity:backend/src-tauri/src/main.rs:3137
clarity:backend/src-tauri/src/processing_api/elog.rs:273-305
| Method | Path | Auth | Description |
|---|---|---|---|
| GET/POST | /elog/group/data/health |
None | Health check |
| POST | /elog/group/data |
None (no JWT at route level) | Group parameter data report for time range |
| POST | /elog/group/data/deviation-report |
None | Limit-breach deviation analysis |
| POST | /elog/group/data/downloadcsv |
None | CSV download of tag data |
| POST | /elog/summary/download |
None | Shift summary as Excel workbook (rust_xlsxwriter) |
Note: Elog routes have no JWT filter at the Warp level in the current source.
Registered by crate::processing_api::ingest::ingest_routes().
clarity:backend/src-tauri/src/main.rs:3138
clarity:backend/src-tauri/src/processing_api/ingest.rs:1191-1218
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /ingest/health |
None | Health check |
| POST | /ingest/v2/{scope1}/{scope2} |
JWT | Tag-to-scope write ingest — fast path (borrowed-key parse + cached ring bindings; ordered before v1). See Processing API § ingest v2 |
| POST | /ingest/{scope1}/{scope2} |
JWT | Tag-to-scope write ingest v1 |
| POST | /ingest/backfill/{scope1}/{scope2} |
JWT | Historical backfill ingest |
/exactapi) · Mail · CloudSync — new subsystemsThese three subsystems were added in this cycle (the notification-engine branch + CloudSync). Each has its own reference page; endpoint summaries below.
Notifications — registered first in the warp composition so custom routes win over the generic v2 CRUD POST /notifications (clarity:backend/src-tauri/src/main.rs:5456-5457); service init at main.rs:5269-5274. Full detail: Notifications Engine.
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /exactapi/notifications |
admin (in-handler role check) | Create/broadcast a notification |
| GET | /exactapi/notifications/stream |
JWT (header or ?access_token=) |
SSE live stream |
| POST | /exactapi/notifications/{id}/read |
JWT | Mark read |
| GET | /exactapi/notifications/unread-count |
JWT | Unread badge count |
Mail / SMTP — routes mounted at warp::path("exactapi").and(mail::routes::routes(db)) (clarity:backend/src-tauri/src/main.rs:5430-5431); init + background sender at main.rs:5278-5282. Full detail: Mail / SMTP.
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /exactapi/mail/send |
JWT | Send/queue an email (multipart) |
| GET | /exactapi/mail/queue/status |
admin | Queue counts by status |
| GET/POST | /exactapi/admin/smtp/config |
admin | Read / hot-reload SMTP config |
| POST | /exactapi/admin/smtp/test |
admin | Send a test email |
| GET | /admin/smtp_ui |
— | Bundled admin config HTML page (outside /exactapi) |
CloudSync — routes merged at main.rs:5074; boot auto-start gated on cloudsync_local_to_host_cloudsync at main.rs:5703-5707. All require bearer auth. Full detail: CloudSync.
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /exactapi/cloudsync/start | /stop |
JWT | Start / stop the sync task |
| GET | /exactapi/cloudsync/status | /config |
JWT | Cycle snapshot / redacted config |
| PUT | /exactapi/cloudsync/config |
JWT | Replace config (in-memory only) |
ems_routes() is defined in clarity:backend/src-tauri/src/processing_api/ems.rs:1455 with 6 routes under /dataflow/ems/*, but it is not called in main.rs. These routes are served by a Python service registered with the python proxy.
| Method | Path | Description |
|---|---|---|
| GET/POST | /dataflow/ems/kpiparams |
KPI parameters query |
| GET/POST | /dataflow/ems/load-share-donut/linewise |
Line-wise load share donut data |
| GET/POST | /dataflow/ems/line-consumption-trend |
Line consumption trend |
| GET/POST | /dataflow/ems/tags/summary |
Tag summary |
| POST | /dataflow/ems/tags/summary/download |
Tag summary download |
| GET/POST | /dataflow/ems/meters/group-config |
Meter group config |
Source: clarity:backend/src-tauri/src/processing_api/ems.rs:1455-1490
NOTE:
ems_routes()is dead code — the function is defined inems.rs:1455but never called inmain.rs. The/dataflow/ems/*paths are served by a Python service at runtime, registered via the Python proxy catch-all (main.rs:3144). Confirmed bywiki/dev/internals/processing-api.md(source:clarity:backend/src-tauri/src/api/python_proxy.rs:136-142).
Registered by backup::get_backup_routes(storage_arc).
clarity:backend/src-tauri/src/main.rs:3143
clarity:backend/src-tauri/src/backup/api.rs
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/admin/backup/status |
Admin JWT | Current backup state |
| GET | /api/admin/backup/list |
Admin JWT | List backup files |
| GET | /api/admin/backup/config |
Admin JWT | Get backup configuration |
| PUT | /api/admin/backup/config |
Admin JWT | Update backup configuration |
| POST | /api/admin/backup/restore/sqlite |
Admin JWT | Restore SQLite from backup |
| POST | /api/admin/backup/restore/timeseries |
Admin JWT | Restore timeseries data from backup |
Source: clarity:backend/src-tauri/src/backup/api.rs:20-260
See wiki/dev/internals/backup-system.md for the full backup subsystem documentation.
Registered by process_manager::http_api::log_routes(process_manager).
clarity:backend/src-tauri/src/main.rs:3102
Registered by process_manager::http_api::log_routes(process_manager).
clarity:backend/src-tauri/src/process_manager/http_api.rs
| Method | Path | Description |
|---|---|---|
| GET | /process-logs |
Serves HTML UI for log viewing |
| GET | /process-logs/ws |
WebSocket: streams {type:"log", entry: LogEntry} JSON; sends {type:"process_list"} on connect plus last 1000 log entries as history |
No process start/stop/status HTTP routes exist in http_api.rs. Process lifecycle is managed programmatically via ProcessManager methods.
Registered by clarity_lib::mqtt_ws_proxy::mqtt_ws_route().
clarity:backend/src-tauri/src/main.rs:3142
WebSocket-to-MQTT bridge for browser clients.
clarity_lib::api::python_proxy::python_proxy_routes() is added last and acts as a catch-all for paths registered by Python services at runtime.
clarity:backend/src-tauri/src/main.rs:3144
Routes are registered by Python services at startup via register_routes(name, port, routes). Matching is O(1) for exact routes, segment-by-segment for parameterized routes.
clarity:backend/src-tauri/src/api/python_proxy.rs:5-264
clarity_lib::static_assets::web_static_handler serves the React frontend. Added last to main.rs route composition.
clarity:backend/src-tauri/src/main.rs:3079-3081
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /docs |
None | Swagger UI (embedded HTML) |
| GET | /exactapi/openapi.yaml |
None | OpenAPI spec (embedded from docs/openapi.yaml) |
Source: clarity:backend/src-tauri/src/main.rs:3027-3044
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /opc-network |
None | Returns a generated ObjectId {"id": "..."} |
Source: clarity:backend/src-tauri/src/main.rs:3124-3129
Last updated: 2026-07-17 from commit 6800acc