Repository navigation
fix(studio-server): a history restore refuses to remove every tracked file - #6175
miguel-heygen wants to merge 5 commits into
Conversation
…s a restore keeps
Edit accuracy: accurate 271 (base branch 271), smooth 152 of thoseThe gate passes. Quarantined, measured but not gated (0) Unstable (2)
|
There was a problem hiding this comment.
Verdict: APPROVE · 0 blocker · 0 important · 1 nit · head 01f5a328
The guard sits in restoreNow, and every whole-project restore goes through it: restore (route, CLI, owner) and undo --back-to-before. Plain undo and step only write an entry's own files, so they can't empty a project this way. The error is thrown before writeAs, so nothing is written, and the route already maps it to a 409. CI is green at this head.
- nit: the empty-project check only fires when no project file would remain. A start that held one stray file (say a
README.md) still lets a restore of the start delete everything added later. The PR says this is intended. A threshold or a confirm step could be a follow-up if the incident's project wasn't fully empty at the start.
Scope
Read all 6 files plus the engine's adoption code (firstOpen, adoptMediaInBackground, recordAdopted, start). Files still being adopted aren't in tracked until they're recorded, so a restore never deleted them. The adopting refusal is extra caution and doesn't change behaviour once the copy finishes; the test covers both sides. No prior human reviews.
— Genesis
There was a problem hiding this comment.
Verdict: APPROVE · 0 blocker · 0 important · 0 nits · 01f5a328 → bb46729e
Only change since my approval is the one-line @hyperframes/studio-server packed budget bump (142100 → 143700) in scripts/package-size-budgets.json. The reason is in the PR body, as verify-packed-manifests.mjs asks. Main's 143380 B plus the 249 B guard comes to about 143629 B. That fits under the new budget and stays well above the 0.85 ratchet floor. CI on this head was still running when I approved.
What
A project history restore no longer deletes every file in the project. It now refuses, writes nothing, and says why, in two cases:
A restore that removes some files (the ones added after the chosen point) still works as before. A normal undo of the change that added a project's only file is unchanged (it does not go through restore).
Why
A project lost its
index.html,compositions/*andassets/*files; the folders stayed. A restore builds its plan as "every tracked file deleted, then the files at the chosen point laid back on top". When the chosen point is the start and the start snapshot is empty (history opened on an empty folder) or still missing files that are copied in the background, that plan deletes everything. File deletes do not remove folders, which matches what was left behind.Every path that restores goes through the same function: the restore route and
hyperframes history restore, and undo with "back to before" (the undo route,hyperframes history undo --back-to-before, and the Desktop chat's go-back and agent undo, which pass that mode). Going back to before a project's first recorded change is a restore of the start.Related work
None open for this case. Other open PRs edit the same file (media adoption on close, media not copied into history); this change is a few lines in the restore function and should rebase cleanly on either.
How
HistoryRefusedError(exported next toHistoryClosedError) before anything is written.HistoryRefusedErroras a refusal: one line on stderr (or JSON with--json) and exit code 2, like the other history refusals. Through a running preview server, the 409 already became a refusal.Test plan
New tests, each entering through a real caller:
projectHistory.test.ts)history.restore, as the restore route and CLI call ithistory.undo(id, { mode: "back-to-before" }), as the undo route, CLI and Desktop call ithistory.restorehistory.restorehistory.restorehistory.undoPOST /history/restore {point: "start"}answers 409 with the message, files stay (routes/history.test.ts)hyperframes history restore startexits 2 with the message, files stay, both in-process and through a preview server (commands/history.test.ts)Failing first: with the source files reset to main and the new tests kept, the four refusal tests in studio-server failed (
promise resolved instead of rejecting,expected 200 to be 409) and the CLI test failed in both modes (expected +0 to be 2). The two "still works" tests passed on main, as they should.With the fix, three runs in a row of each touched file:
projectHistory.test.ts+routes/history.test.ts179 passed, 2 skipped (the same 2 skipped as on main), andcommands/history.test.ts50 passed. The ledger-only test also fails on main and on this PR's first commit (promise resolved instead of rejecting).tsc --noEmitfor studio-server and cli,oxlintandoxfmt --checkon the changed files: clean.Not exercised: the Desktop go-back UI against a real project (Desktop lives in another repository; a thrown error from this call reaches it the same way as its existing refusals such as "That change is already undone.", but I did not click through it).
Package size budget
@hyperframes/studio-serverpacked budget goes from 142100 to 143700 inscripts/package-size-budgets.json. The guard adds real code to the package (+249 B packed, about +1 KB unpacked). The budget was already stale: main packed at 143380 B, 0.9% over the old budget and 141 B from the 1% limit, so any change here failed the merge queue'sverify:packed-manifests. The new budget is what ships today, with the usual 1% tolerance on top.