Toasts showed backend prose verbatim, with no code to translate
against and "application" where the UI says "service". Not-found,
missing-field, permission, conflict and agent-offline errors now come
from factories in app/exceptions.py that pair one fixed message with
a dotted code (not_found.<resource>, validation.required,
permission.denied, conflict.exists, agent.offline), so the client can
translate them.
The shared app-loading helpers and the apps, domains, servers,
databases and backups routes raise those errors instead of hand
shaping bodies, which drops the hand-shaped body count from 1132 to
850. Five routes picked an HTTP status by comparing error text; they
now key off a result code or a sentinel so rewording can't change a
status. Five broad excepts that echoed str(exc) now log the crash and
return the request id instead.
Plan 75 §G5 + §G6.
§G5 — app/services/db_exec.py. engine × host dispatch (mysql/postgres
crossed with native/docker) was written three times. Only
managed_db_user_service delegated fully to DatabaseService's executors;
the other two grew private copies that had already drifted —
db_process_service's docker-postgres branch built its own psql argv and
never set ON_ERROR_STOP=1, so psql exited 0 for a statement the server
had rejected and the caller was told it worked.
db_exec owns the dispatch and nothing else; the four executors stay in
DatabaseService. The three _exec_sql seams remain (tests stub them) and
now delegate. An unrecognised engine is reported, never quietly routed to
mysql, where it would surface as a syntax error blamed on the caller.
docker_mysql_execute/docker_pg_execute gain machine_readable, which is
the single flag db_config_tuner_service was keeping a whole private copy
of the dispatcher to add. The MYSQL_PWD env block, pasted six times in
database_service, becomes _mysql_env(): it carries a real rule — the
password goes in the environment, never on the argv — and a rule in six
copies only holds in the copies someone remembered.
§G6 — adopting doors Round 1 built:
- config_store's write is now atomic (tmp + os.replace). The §F4
consolidation deliberately kept the copies' truncating write and noted
atomicity would land here once; this is that once. A write that dies
mid-dump leaves a truncated file, and every load path treats a corrupt
config as "use the default" — a config silently reset.
- uptime_service._save_history was `except Exception: pass` over that
truncating write, so a full disk destroyed the history and reported
nothing while the panel kept rendering a figure from a file that had
stopped updating. It now returns and logs the failure.
- unit_is_active(): `systemctl is-active` was re-inlined in four services
and every copy collapsed "could not ask" into "not running". The shared
probe is tri-state; None is not False.
- slug: preview_service and saved_view_service re-implemented slugify's
regex, and saved_view re-implemented unique_slug's collision loop too.
unique_slug gains start= so saved_view keeps generating the handles it
always has.
Checked rather than assumed: the plan lists file_integrity_service as
silently losing data on a full disk. It does not — it already used tmp +
os.replace and does not swallow the error. It is the pattern followed
here, not a victim; only uptime_service had the silent loss.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
SHOW FULL PROCESSLIST / pg_stat_activity normalized per server or
container, with admin-only kill/terminate and 5s auto-refresh tabs in
the Database Explorer.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>