area_maintenance
The system-administrator area (
dd88, "Maintenance") — the back-office dashboard that hosts the operational widgets an administrator uses to back up, migrate, reconcile, rebuild and inspect a Dédalo installation.See also: area · Areas · dd_area_maintenance_api · Architecture overview
This is the subsystem reference for the Maintenance area: how the widget catalog is assembled, how a widget request is dispatched and gated, what each widget does, and how to add one.
Role
area_maintenance is the area model of the top-level "Maintenance" menu node,
ontology tipo dd88. Like every area it is an ontology node with
no matrix table and no records of its own. What makes it special is its payload:
instead of aggregating descendant sections into a statistics dashboard, its
data item carries a datalist of widget descriptors, and each widget is a
self-contained back-office operation.
In the area behavior taxonomy (src/core/concepts/area.ts) it carries the
maintenance behavior — its own branch, its own subsystem.
Administrators only
The whole area is a global-admin surface and fails closed. The read is
refused with 403 for a non-admin, and every widget dispatch re-checks
principal.isGlobalAdmin independently — the gate is not delegated to the
menu having hidden the area.
Where the engine lives
| module | role |
|---|---|
src/core/area/read.ts |
The maintenance behavior branch of dispatchAreaRead: admin gate, structure context, and the widget catalog as data[0].datalist. |
src/core/area_maintenance/widgets/registry.ts |
The catalog assembly and the three dispatchers (buildMaintenanceDataItem, dispatchWidgetRequest, dispatchGetWidgetValue). |
src/core/area_maintenance/widgets/support.ts |
The shared contract: WidgetModule, WidgetSpec, WidgetHandler, WidgetResponse. |
src/core/area_maintenance/widgets/<widget_id>.ts |
One module per widget — the unit of work. |
src/core/api/handlers/dd_area_maintenance_api.ts |
The three API actions, registered under the dd_area_maintenance_api key. |
src/core/area_maintenance/backup.ts, user_stats.ts |
Heavier operations the widgets call into. |
The widget model
A widget is one module file exporting one WidgetModule:
export const widget: WidgetModule = {
spec: {
id : 'counters_status', // the wire id the client sends
category: 'integrity', // the dashboard group
label : { kind: 'label', key: 'counters_status' },
},
apiActions: { // the EXPLICIT method registry
modify_counter: countersModifyCounter,
},
getValue : countersGetValue, // the panel-open value load
eagerValue: countersEagerValue, // optional: a value pre-computed into the catalog
};
specis the catalog entry: id, category, label rule and optional CSS class. Theidis what the client sends assource.model.apiActionsis the widget's method registry. A method exists on the API if and only if it is listed here — there is no "any exported function is callable" fallback. This is the security boundary, not a convenience.getValueanswers the panel-open value load. A widget without one returns an explicit "unavailable" error rather than silently nothing.eagerValuepre-computes a value into the catalog so the folded dashboard card and the opened panel paint from identical data. It is fail-soft: a widget whose value cannot be computed must never break the dashboard read.
getValueis a PAIR — the server field and the client's<widget>.prototype.get_valuebinding are one switch with two halves, and either half alone is a broken panel. The value fetch is driven bywidget_common.prototype.load(), which the dashboard fires when a panel opens (plus, at idle, forbackgroundwidgets and for panels restored in the open state). That loader no-ops whentypeof self.get_value !== 'function', so:
- client binding without a server
getValue→ opening the panel throwsmaintenance.widget_unavailableand the widget never renders. It does not degrade to an empty panel; the whole card dies, actions included.- server
getValuewithout the client binding → the loader never runs,self.valuestays whatever the catalog carried (nullfor a widget with noeagerValue), and the panel paints EMPTY while a perfectly good value sits unrequested on the server.
test/unit/maintenance_widget_get_value_tripwire.test.tsgates the pairing in both directions, so a half-wired widget fails there rather than in an admin's browser. Deliberately having NEITHER half is agreement and passes.Budget
getValueas load-time cost (WC-071). Whatever it does happens per panel open, per admin, in both Map and List views — and immediately at dashboard load forbackgroundand restored-open panels.dataframe_controlis the worked example of getting this wrong: itsgetValueran a whole-database integrity scan that could not finish inside the statement timeout. It now has NEITHER half of the pair and scans only on an operator's click. If the work is unbounded or scales with table size, it belongs inapiActionsbehind a button, not ingetValue.
Every handler returns the same envelope: { result, msg, errors }.
Adding a widget is one new module file plus one import line in
registry.ts. There is nothing else to register.
Dispatch and its four gates
dd_area_maintenance_api exposes exactly three actions:
| action | what it does |
|---|---|
widget_request |
Dispatch source.action to a method in the named widget's apiActions. |
get_widget_value |
Call the named widget's getValue. The panel-load and dynamic-refresh path. |
lock_components_actions |
The one area-level (non-widget) action: get_active_users / force_unlock_all_components. |
dispatchWidgetRequest (registry.ts) applies four gates, in order:
- Admin only — a non-admin principal is refused outright.
- Options must be an object when present.
- The widget id must be in the catalog — checked against the static module list, so an action never pays for the catalog's eager values.
- The method must be registered in that widget's
apiActions— otherwiseunauthorized_method.
dispatchGetWidgetValue applies the admin gate, validates the widget id against
an identifier pattern, and calls the widget's getValue.
flowchart TB
C["Client widget JS<br/>rqo { dd_api: dd_area_maintenance_api }"]
C -->|"action: widget_request"| WR["dispatchWidgetRequest"]
C -->|"action: get_widget_value"| GV["dispatchGetWidgetValue"]
C -->|"action: lock_components_actions"| LC["dispatchLockComponentsActions"]
WR -->|"admin → options → catalog id → apiActions"| WM["widgets/<id>.ts — the registered handler"]
GV -->|"admin → id pattern → getValue"| WM
LC -->|"admin"| LCW["widgets/lock_components.ts"]
Dispatchability is not authorization
apiActions answers "is this method reachable at all?". It is orthogonal
to what the operation itself checks. Several widgets add their own
conditions on top — the data-version upgrade requires maintenance mode, and
the state-writing operations are root-only.
The catalog
getMaintenanceWidgets() builds the ordered descriptor list the client renders,
resolving each label in the application language. buildMaintenanceDataItem()
wraps it as the area's single data item:
{
"context": [ { "tipo": "dd88", "model": "area_maintenance", "label": "Maintenance" } ],
"data": [
{
"section_id": null,
"section_tipo": "dd88",
"tipo": "dd88",
"value": [],
"datalist": [
{ "id": "make_backup", "category": "data", "type": "widget",
"tipo": "dd88", "parent": "dd88", "label": "Make backup", "value": null },
{ "id": "media_control", "category": "integrity", "type": "widget",
"tipo": "dd88", "parent": "dd88", "label": "Media access control", "value": null }
]
}
]
}
Categories
Every widget carries a category; the client groups and filters by it.
| category | what it holds |
|---|---|
data |
Backups, database version artifacts, the data-version upgrade, hierarchy import/export. |
migration |
The bulk transforms — move_tld, move_locator, move_to_portal, move_to_table, move_lang. |
config |
Configuration and code: check_config, config_areas, menu_skip_tipos, update_ontology, register_tools, update_code. |
integrity |
lock_components, sequences_status, media_control, counters_status, reconcile_status, dataframe_control. |
system |
Environment, database info, system info, the runtime panel, error reports. |
diffusion |
publication_api, diffusion_server_control. |
dev |
The API and SQO test consoles, the unit-test runner. |
The widgets
The operations each widget registers. A widget with no apiActions is a
read-only panel: it reports state through getValue or an eager catalog value.
| widget | category | registered actions |
|---|---|---|
make_backup |
data | make_psql_backup, get_dedalo_backup_files |
build_database_version |
data | build_recovery_version_file, restore_dd_ontology_recovery_from_file |
update_data_version |
data | update_data_version |
export_hierarchy |
data | sync_hierarchy_active_status, export_hierarchy — the dump lands in the engine's own install/import/hierarchy, the directory add_hierarchy imports from; see moving a hierarchy between installations |
add_hierarchy |
data | install_hierarchies, reset_hierarchies — import + activate the selected TLDs; reset is the destructive re-import |
move_tld, move_locator, move_to_portal, move_to_table, move_lang |
migration | one transform action each, named after the widget; the panel value carries the explanation body and the JSON definition files to pick from, so both halves of the getValue pair are wired |
check_config |
config | set_maintenance_mode, set_recovery_mode, set_notification |
config_areas |
config | save_config_areas |
menu_skip_tipos |
config | save_menu_skip_tipos |
update_ontology |
config | update_ontology |
register_tools |
config | register_tools |
update_code |
config | update_code, build_version_from_git_master |
lock_components |
integrity | (area-level action — see the dispatch table) |
sequences_status |
integrity | (read-only panel) |
media_control |
integrity | set_media_access_mode, rebuild_media_index |
counters_status |
integrity | modify_counter |
reconcile_status |
integrity | run_reconcile — the one door onto the cross-store reconcile registry (src/core/reconcile): lists every registered reconcile with its last verdict and runs one, dry unless apply:true |
dataframe_control |
integrity | get_value, run_check, run_fix — and NO module getValue, so the panel loads nothing (WC-071); run_check reports per-table coverage and can never claim a completeness it did not earn (WC-072) |
database_info |
system | analyze_db, analyze_statistics, optimize_tables, consolidate_tables, recreate_db_assets, backfill_search_stores, rebuild_db_indexes, rebuild_db_functions, rebuild_db_constraints, rebuild_user_stats, relation_integrity_report |
environment, system_info |
system | (read-only panels) |
error_reports |
system | get_reports |
publication_api |
diffusion | (read-only panel) |
diffusion_server_control |
diffusion | cancel_process, requeue_job, purge_jobs, set_scheduler, retry_pending_deletions |
dedalo_api_test_environment, sqo_test_environment |
dev | (interactive consoles) |
unit_test |
dev | create_test_record |
The system category also carries a runtime panel reporting the running
engine's version, pid, memory and uptime, and offering real cache and session
clears. The cache clear runs the engine's invalidation hub, so it drops every
registered in-memory cache — including the UI-label dictionaries, which is what
makes a label deploy visible without a restart.
The Register tools panel
register_tools is the one config widget that reports a DISCREPANCY rather than
a setting, so it is worth knowing how to read it. Each row joins the two sides
the importer reconciles: the registry record (dd1324) and the tool's directory.
Two version columns follow from that — Installed (the registry) and
Version (what the directory's register.json declares) — and the panel
classifies every row as one of:
| state | what it means | what fixes it |
|---|---|---|
| (none) | registered, on disk, versions agree | — |
| outdated | the files ship a version the registry does not have | Register tools |
| not registered | a tool directory with no registry record | Register tools |
| missing on disk | a registry record whose directory is gone | delete the record, or restore the files |
A sentence above the table names the affected tools and the remedy, and the subsystem card in the maintenance map carries the same verdict — both read one classification served with the panel data, so the map can never report health over a panel showing drift.
The registry is what runs
Tool metadata is served from the registry, not by re-reading register.json
— see register.json for what that
means when you edit a tool.
Some operations are closed by design
A handful of registered methods refuse deliberately, with an explicit
engine_denied envelope naming the reason — they would write files outside
the engine's control. The refusal is loud and named; it is never a silent
no-op.
Extend a widget in its own module
When you add an operation to a widget, add it to that widget's
apiActions — nowhere else. If you do not, dispatchWidgetRequest rejects
it at gate 4 with unauthorized_method.
Runtime state the widgets write
check_config, config_areas, menu_skip_tipos and media_control persist
their changes to the server-state store (../private/ts_state.json, via
src/core/resolve/server_state.ts) rather than to ../private/.env, which is
append-only and therefore cannot hold a UI-settable value. The store holds
maintenance mode, recovery mode, the login notification, the area deny/allow
lists, the menu skip tipos and the media-access override. A null entry means
"no override" — the static configuration wins.
How it fits with the rest of Dédalo
- area / Menu —
area_maintenanceis one of the root menu areas, reachable only by an administrator. It cannot be removed by theareas.denylist: theconfig_areaswidget is anti-lockout guarded. - Ontology —
update_ontologyrewritesdd_ontology, the active schema;register_toolsimports the tool nodes. - Media protection — the
media_controlwidget sets the media-access mode and rebuilds the media publication index. - The diffusion engine — what
publication_apianddiffusion_server_controlinspect and drive. - Creating tools — the tools
subsystem
register_toolsimports into. Widgets are a different extension surface: no register file, no tool paths — one module and one import line. - SQO — the query format the SQO test console exercises.
Related
- area — the area reference.
- Areas — the family index.
- dd_area_maintenance_api — the API-class reference.
- Ontology — the active schema
update_ontologyrewrites. - Media protection — configured by the
media_controlwidget. - The diffusion engine — driven by the diffusion widgets.
- Creating tools — the tools subsystem.
- Architecture overview — areas → sections → components → data.