Hub stage 4: config history, rollback, drift check, baseline export and import

Every saved config, and every earlier baseline, is listed per app. You
can view it, diff it against a server's current config, and roll it back
through preview and apply. Earlier baselines can be restored.

A drift check (HUB_DRIFT_MINUTES, default 15) compares each server with
its effective config. Drift shows in the header and the matrix, with
Accept as baseline and Revert to baseline. All baselines export to one
file. Importing that file shows a dry run before it writes.

Rolling back preselects, and diffs against, the server the version came
from. The line-diff op "del" is renamed to "remove" for the language lint.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
DenozordecandClaude Opus 5.5 committed 2026-10-31 19:57:33 +07:00
1 parent 0e05039202
commit 57e492cceb
29 files changed
+2294 -125

No files matched your search

+2 -2
View File
@@ -77,7 +77,7 @@ export class AppsWriter {
}
/* Saves the app's current config next to the hub's data; throws if it cannot. */
async saveHistory(server: HubServer, name: string, kind: 'before-uninstall' | 'before-apply' = 'before-uninstall'): Promise<string> {
async saveHistory(server: HubServer, name: string, kind: 'before-uninstall' | 'before-set' = 'before-uninstall'): Promise<string> {
if (server.adminToken === undefined) throw new Error('no admin token configured')
this.#upstream.invalidate(server.id)
const response = (await this.#upstream.call(server, 'apps/config/get', { name }, server.adminToken)) as { config?: unknown } | null
@@ -105,7 +105,7 @@ export class AppsWriter {
case 'config-set': {
if (config === undefined || config.length > MAX_CONFIG) throw new Error('the config is missing or larger than 1 MB')
try {
await this.saveHistory(server, name, 'before-apply')
await this.saveHistory(server, name, 'before-set')
} catch (e) {
throw new Error(`the old config could not be saved first (${reason(e)}); nothing was changed`)
}