fix(dashboard): verify assistant-health saves with read-back

This commit is contained in:
William Valentin
2026-02-18 22:23:24 -08:00
parent 7e480f11fc
commit 9a0fe3ec56
2 changed files with 57 additions and 2 deletions
+11
View File
@@ -5683,6 +5683,17 @@
"docs/plans/state.json"
],
"test_status": "pnpm typecheck passing"
},
"dashboard-assistant-health-readback-verification": {
"status": "completed",
"date": "2026-02-19",
"updated": "2026-02-19",
"summary": "Added read-after-write verification to Assistant Health saves. After `config.patch`, dashboard now performs `config.get` and confirms each patched key/value actually stuck; mismatches are surfaced as explicit errors instead of false green success.",
"files_modified": [
"src/gateway/ui/pages/dashboard.js",
"docs/plans/state.json"
],
"test_status": "pnpm typecheck passing"
}
},
"overall_progress": {
+46 -2
View File
@@ -123,6 +123,30 @@ function buildRollbackPatchesFromSnapshot(snapshot) {
};
}
function getByPath(obj, dottedPath) {
if (!obj || typeof obj !== 'object') {return undefined;}
const parts = dottedPath.split('.');
let cursor = obj;
for (const part of parts) {
if (!cursor || typeof cursor !== 'object' || !(part in cursor)) {
return undefined;
}
cursor = cursor[part];
}
return cursor;
}
function valuesMatch(expected, actual) {
if (Array.isArray(expected) && Array.isArray(actual)) {
if (expected.length !== actual.length) {return false;}
for (let i = 0; i < expected.length; i++) {
if (expected[i] !== actual[i]) {return false;}
}
return true;
}
return expected === actual;
}
function setAssistantSaveState(message, tone = 'neutral') {
_assistantSaveState = {
message,
@@ -550,8 +574,28 @@ async function applyAssistantPatch(patches, statusEl) {
message = `Runtime-only save (${applied.length} updated, file persistence unavailable)`;
tone = 'warning';
} else {
message = `Saved to runtime + config file (${applied.length} updated)`;
tone = 'success';
// Verify read-after-write so UI cannot claim persistence when value did not stick.
try {
const fresh = await _dashboardClient.call('config.get');
const mismatches = [];
for (const [key, value] of Object.entries(patches)) {
const actual = getByPath(fresh, key);
if (!valuesMatch(value, actual)) {
mismatches.push(`${key} expected=${JSON.stringify(value)} actual=${JSON.stringify(actual)}`);
}
}
if (mismatches.length > 0) {
message = `Saved response received but read-back mismatch: ${mismatches.join('; ')}`;
tone = 'error';
} else {
message = `Saved to runtime + config file (${applied.length} updated)`;
tone = 'success';
}
} catch (verifyError) {
message = `Saved response received, but verification failed: ${verifyError instanceof Error ? verifyError.message : String(verifyError)}`;
tone = 'warning';
}
}
setAssistantSaveState(message, tone);
statusEl.textContent = message;