Self-service operation from the WebUI, for a dev instance first. - Settings > Console > Restart Console. A foreground `mosaic console start` now supervises the webui: the child exits 75 after answering a restart and the supervisor relaunches it with the same argv. Org buses keep running. /api/config carries a per-process boot id, so the open page polls until a new process answers and then reloads. A detached Console has no supervisor and answers 409. - Restart bus on every "Restart needed" receipt and on the bus-down banner: POST /api/orgs/<id>/restart/bus restarts a running bus or starts a stopped one, then shows a done receipt with the time. - Settings > Routing: after a save, the page lists how the change reaches each agent (next start, session end, thread idle) and offers "Restart <agent> now" for a running one. A session is terminated with the reason "route change" and launched again; a thread is terminated. - Organization settings: the signed-in owner's settings, routing, agent, plug-in and Model proxy writes now reach the selected org's bus instead of answering 403. Org mode exists only under --dev-local-owner. Proxy setup stays a terminal step; a dev-only note names the two commands. Every control needs the signed-in owner session, the page's exact Origin and Sec-Fetch-Site: same-origin. The tester listener, granted users, API tokens, agents and bus roles are refused before any work. Each use is written to <dataRoot>/console/actions.jsonl (0600, no symlinks) with who, when, what and the outcome. The Console and bus restart trails fail closed. No change to credential handling, the broker, the bus tenant boundary or v2 schemas.
25 lines
1.5 KiB
JavaScript
25 lines
1.5 KiB
JavaScript
// F3: the Console's trail of human operations (Console restart, org bus restart, route and
|
|
// instance changes made from the page). One JSON line per use: who, when, what, outcome.
|
|
// The file is <dataRoot>/console/actions.jsonl, owner-only. A write that cannot be made safely
|
|
// throws, and the caller refuses the operation (fail closed).
|
|
import { constants, closeSync, fstatSync, lstatSync, mkdirSync, openSync, writeSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
|
|
// The person, never a token: provider, issuer, subject and display name of the session.
|
|
export const actor = session => ({ provider: session.provider, iss: session.iss, sub: session.sub, ...(typeof session.name === 'string' ? { name: session.name } : {}) });
|
|
|
|
export function actionTrail(dir) {
|
|
const file = join(dir, 'actions.jsonl');
|
|
return entry => {
|
|
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
const st = lstatSync(dir);
|
|
if (!st.isDirectory() || st.isSymbolicLink() || st.uid !== process.getuid() || (st.mode & 0o077) !== 0) throw new Error('unsafe trail directory');
|
|
const fd = openSync(file, constants.O_WRONLY | constants.O_APPEND | constants.O_CREAT | constants.O_NOFOLLOW, 0o600);
|
|
try {
|
|
const f = fstatSync(fd);
|
|
if (!f.isFile() || f.uid !== process.getuid() || (f.mode & 0o777) !== 0o600 || f.nlink !== 1) throw new Error('unsafe trail file');
|
|
writeSync(fd, `${JSON.stringify({ at: new Date().toISOString(), ...entry })}\n`);
|
|
} finally { closeSync(fd); }
|
|
};
|
|
}
|