From 940bf43ba5cbed2d0a9afa4c70ffbc3df5cb3f90 Mon Sep 17 00:00:00 2001 From: Agando Date: Fri, 23 Oct 2026 14:39:45 +0200 Subject: fix(newos4): unmeasured skills are untracked, world-model writes verified, skill audit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit External review packet (os4-external-review-v4.6.51.html) triaged against the code: 2 findings confirmed and fixed, 4 could not be reproduced and were recorded as such. - module-memory-graph.js: getStaleSkills() treated a missing lastUsed as 999 days unused, so skill-audit recommended archiving every skill (22 of 22). Skills with no usage telemetry are now collected by getUntrackedSkills() and never counted stale. Live: "Stale: 0" + "Untracked: 22 (no usage telemetry — NOT stale)". - module-world-model.js: _saveToVFS() ignored fsWriteJSON()'s return value and promoted whatever a truthy read returned. Now write -> read-back -> byte compare -> promote, with every return checked. Regression test modules/test/test-world-model-save.js (red on the old code: promoted=true with errs=[]; green now). - scripts/newos4-skill-audit.py: 5 mechanical probes (version x3, read_file paging + context budget, selection symbols, required scripts, extKeys) + skill presence; exits non-zero on drift. 7/7 pass today. Weekly job cb6bc2a16216 runs it Mondays. Not reproduced (recorded in admin/docs/newos4-external-review-verification-2026-10-23.md): world_model_query empty-payload throw (live call returns JSON), pipes-as-command-names, ls skills empty (23 entries via fs.readDir), ~66 agent dirs (4 live). --- modules/core/module-world-model.js | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) (limited to 'modules/core/module-world-model.js') diff --git a/modules/core/module-world-model.js b/modules/core/module-world-model.js index cbfd1fb..4e88f59 100644 --- a/modules/core/module-world-model.js +++ b/modules/core/module-world-model.js @@ -580,14 +580,28 @@ class WorldModelModule { if (prevModel) { try { fs.fsWriteJSON('/system/world-model.prev.json', prevModel); } catch (_) {} } // v4.5.89b fix: Clean stale temp from previous crash (P1 from review) try { fs.deleteFile('/system/world-model.tmp.json'); } catch (_) {} - fs.fsWriteJSON('/system/world-model.tmp.json', this._model); - const tmp = fs.fsReadJSON('/system/world-model.tmp.json'); - if (tmp) { - fs.fsWriteJSON('/system/world-model.json', tmp); - fs.deleteFile('/system/world-model.tmp.json'); - } else { - console.error('[WorldModel] Save failed: temp file corrupt after write'); + // Atomic + verified: never promote a write we did not read back byte-for-byte. + // Measured 2026-10-23: the old block promoted /system/world-model.json even when every + // write returned false, and logged nothing — that is how a graph got replaced by a + // partial model. fs.fsWriteJSON() returns a boolean; ignoring it hides quota failures. + const payload = JSON.stringify(this._model); + const wrote = fs.writeFile('/system/world-model.tmp.json', payload); + if (!wrote) { + console.error('[WorldModel] Save failed: write returned false (storage quota?)'); + return; } + const back = fs.readFile('/system/world-model.tmp.json'); + if (back !== payload) { + console.error('[WorldModel] Save failed: read-back mismatch (' + + (back ? back.length : 0) + '/' + payload.length + ' bytes) — keeping previous model'); + return; + } + const promoted = fs.writeFile('/system/world-model.json', back); + if (!promoted) { + console.error('[WorldModel] Save failed: promote returned false'); + return; + } + fs.deleteFile('/system/world-model.tmp.json'); } catch (e) { console.error('[WorldModel] Save error:', e.message); } -- cgit v1.2.3