diff options
| author | Agando <roduccomm@gmail.com> | 2026-10-27 11:23:10 +0100 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-10-27 11:23:23 +0100 |
| commit | d2de622744212ea0ba720b3f4212b82940ffe84b (patch) | |
| tree | 9aaa4a6513e5251297bb9160444c12b1d26a3a2e /modules/core/virtual-fs.js | |
| parent | 91bfed13d679a76529c6284c7de5f4a87e0d3a0f (diff) | |
fix(newos4): rollback keeps scaffolding, checkpoints cannot collide, tests gate the build
Review of the 08:30 implementer run (v4.6.102, checkpoint name fix). The shipped fix was
correct; these are the defects it introduced or left behind, each measured first.
- virtual-fs.js rollback(): the snapshot strips _snapshots/_sessions/.trash for bloat. Restoring
it verbatim deleted every checkpoint, every agent session and the trash, made rollback
irreversible and unrepeatable. Re-attach the live copies after the root swap.
- checkpoint(): names are millisecond-resolution, so tight calls overwrote each other
(measured 6 calls -> 3 snapshots). Dedupe with a -N suffix, refuse names containing "/"
(written but unlistable), and prune by snapshot createdAt instead of filename sort, which
let a custom name ("aaa-oldest") evict newer auto-named checkpoints.
- module-predictor.js: legacy pre-v2 points have no `usable`, so a 0.079 s aborted run
re-entered similarity as evidence and floored every prediction at 1 s. Backfill `usable`
from the measurement on load, drop the ones it rejects, add a belt-and-braces guard in
_findSimilar and count only usable points as actual executions.
- newos4-maintain.py: run every node-runnable modules/test/test-*.js on each maintenance pass.
The v4.6.102 regression was written but no job ever executed it.
- newos4-steer.py: `done` now requires --commit <sha> that exists in the repo, and gains
`reopen`. A written plan was being closed as done at 06:17 while the predictor fix shipped
nowhere, so the implementer's pre-flight queue was empty.
- steering.json: predictor item reopened, checkpoint item stamped with f7249ff.
- maintenance board: MAINT-002/005/015 were fixed in v4.6.81-85 but still listed open/high;
closed with their commit ids, and MAINT-010/016 annotated with the probes that keep them open.
Tests: test-vfs-checkpoint 23/23 (RED 14/9 before), test-predictor-accuracy 38/38
(T1-T3 RED on HEAD), maintain --fix all PASS, browser round-trip
"Checkpoint created: <name>" -> "Rolled back to checkpoint: <name>" on v4.6.103.
Also carries the already-deployed 2026-10-26 syntax-gate hunk in newos4-maintain.py (syntax_failures + gated rebuild) that had never been committed.
Diffstat (limited to 'modules/core/virtual-fs.js')
| -rw-r--r-- | modules/core/virtual-fs.js | 32 |
1 files changed, 28 insertions, 4 deletions
diff --git a/modules/core/virtual-fs.js b/modules/core/virtual-fs.js index 09b3548..3ed1b40 100644 --- a/modules/core/virtual-fs.js +++ b/modules/core/virtual-fs.js @@ -940,6 +940,17 @@ class VirtualFileSystem { } checkpoint(name) { if (!name) name = new Date().toISOString(); + name = String(name).trim(); + if (!name || name.indexOf('/') !== -1) { + throw new Error('checkpoint name must be non-empty and must not contain "/"'); + } + // Two unnamed calls can land in the same millisecond; without this the second silently + // overwrites the first (measured: 6 calls -> 3 distinct names). + if (this.listCheckpoints().indexOf(name) !== -1) { + let n = 2; + while (this.listCheckpoints().indexOf(name + '-' + n) !== -1) n++; + name = name + '-' + n; + } const createdAt = new Date().toISOString(); // Strip system dirs to prevent fractal bloat const rootClone = JSON.parse(JSON.stringify(this.root)); @@ -959,12 +970,15 @@ class VirtualFileSystem { } this.fsWriteJSON('/_snapshots/checkpoint-' + name + '.json', data); this.saveToStorage(); - // Auto-manage: keep max 3 checkpoints, delete oldest + // Auto-manage: keep the 3 NEWEST checkpoints — by createdAt, not by name, so a custom + // name ("aaa-oldest") cannot pin itself forever while newer snapshots are deleted. const all = this.listCheckpoints(); if (all.length > 3) { - const sorted = all.slice().sort(); - const toDelete = sorted.slice(0, all.length - 3); - for (const old of toDelete) this.deleteCheckpoint(old); + const aged = all.map(n => { + const snap = this.fsReadJSON('/_snapshots/checkpoint-' + n + '.json'); + return { name: n, at: (snap && snap.createdAt) || '' }; + }).sort((a, b) => (a.at < b.at ? -1 : (a.at > b.at ? 1 : 0))); + for (const old of aged.slice(0, aged.length - 3)) this.deleteCheckpoint(old.name); } return name; } @@ -974,8 +988,18 @@ class VirtualFileSystem { console.error('Rollback failed: checkpoint not found or invalid'); return false; } + // The snapshot deliberately omits scaffolding (bloat guard at :944-950). Restoring the + // clone verbatim deletes every checkpoint, every agent session and the trash, so keep the + // live copies and re-attach them after the root swap. + const keep = {}; + for (const dir of ['_snapshots', '_sessions', '.trash']) { + const node = this.root && this.root.content ? this.root.content[dir] : null; + if (node) keep[dir] = node; + } this.root = raw.root; this.currentPath = raw.currentPath || []; + if (!this.root.content) this.root.content = {}; + for (const dir of Object.keys(keep)) this.root.content[dir] = keep[dir]; this.saveToStorage(); return true; } |