Repository navigation
VC-822: Detach a removed project instead of deleting its tickets and Sessions - #937
Merged
hussainph merged 5 commits intoOct 19, 2026
Conversation
…-a-project-hard-deletes-all-its-tickets
Removing a project ran DELETE FROM projects, and the foreign-key cascades took every live and archived ticket, comment, Automation and Session with its whole ledger, behind a dialog that said only that the folder on disk was untouched. Removal now detaches the project (migration 064, expand-only, nullable projects.removed_at). The host still stops the project's live work first (VC-796), then stamps removed_at instead of deleting the row: - every listing (rail, bootstrap, workspace lists, CLI doors, worktree allow-lists) hides a removed project and its tickets; - adding the same folder again re-attaches the same row, with its id, base branch, ticket counter, tickets and Sessions, at the end of the rail, and announces it so every window re-reads the board; a prefix a project on the rail took meanwhile refuses the re-attach; - nothing starts work in a removed project: the Session location resolver refuses prepare and reaffirm, Automations refuse to run or be edited there, and an armed column's countdown is dropped; - the credentials of its MCP servers are forgotten, since no Settings screen shows them any more; the server rows stay for a re-add; - the confirm says the tickets and Sessions are kept and how to get them back; the export carries removedAt.
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configuration
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…s column The 053-collision suite compared every projects row and the table's DDL after migrating to the head, so migration 064's removed_at column broke its 'nothing changed' claim. The repair is now judged at 054, where it ends, and the head check expects only the added NULL column.
… doors - A new project may not take a removed project's ticket prefix: prefixes cannot change, so taking it would leave the old folder impossible to add again. The refusal names the removed project and the one fix. - hostd serves no Workspace for a removed project (servedWorkspace), a terminal does not open in one (PTY scope), and its board takes no write and answers no snapshot or roster; a write it already took still replays. - A Run in a removed project is refused as PROJECT_REMOVED, so a schedule's skip reads "Skipped — The project was removed from Volli." instead of "not found". - Relinking a project onto a removed project's folder is refused with a sentence instead of failing on the path's UNIQUE constraint. - Shorter confirm copy; the dry-run says the old name is kept; the exact path match of a re-add is documented; tests for a removed project's worktree container and for a removed project's due schedule end to end.
…-a-project-hard-deletes-all-its-tickets
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #920 (VC-796, head
7b90883c9): that PR's host-ownedproject.removeis where this change lives, so its commits are in this branch (fast-forward, no rewrite). Review the last three commits (a850ed9e9,3540ae7fc,2caa84b34) for VC-822. Once #920 merges, the diff againstmainis those commits alone.Problem
Removing a project ran
DELETE FROM projects WHERE id = ?. The foreign-key cascades then deleted:mcp_serversandworkspace_epochs.The confirm dialog only said "The folder on disk is untouched." Nothing could undo the removal. Re-adding the same folder created a new project with
next_ticket_number = 1, while the oldvolli/<PREFIX>-N-*branches were still in the repository. The cascaded MCP servers' credentials stayed in the credential store, and no screen showed them.Fix
The owner chose detach. Removal now stamps a new nullable
projects.removed_atcolumn (migration 064, expand-only, probe-gated like 053 and 063) instead of deleting the row. The cascades themselves are unchanged; removal no longer triggers them.removeProject(packages/host-core/src/project-remove.ts) keeps #920's order: stop the live work, then detach (where #920 deleted), then forget the MCP credentials, end the feed and announce.Hidden everywhere a project is listed.
listProjectsandcountProjectsskip detached rows, and every listing reads through them: the rail and bootstrap, workspace lists (desktop and hostd), CLI doors, the board feed, boot recovery, and worktree allow-lists and sweeps.buildBootstrapPayloadalso drops a detached project's tickets and labels.getProjectByIdandfindProjectByPathstill return a detached row, because looking a project up by id or path is not listing it.Re-adding the same folder re-attaches it (
project-create.ts). The same row comes back with its id, name, base branch, ticket counter, tickets, Sessions and Automations, at the end of the rail. No base-branch detection runs. The re-attach is announced throughonCreated, so every window re-reads the board; desktop'svolli:project-createnow broadcastsdata-changed { projectId }.volli project add --dry-runpreviews the re-attach.Nothing reads, writes or starts work in a detached project:
SessionLocationResolver.prepareandreaffirmrefuse with "This Session's project was removed from Volli. Add its folder again to use it." That covers new bindings, scheduled resumes, delegates, and turns on a binding that outlived the removal.resolvestill answers, since reading where a Session ran starts nothing.findProjectnow means "on the rail", so edits are refused. A Run (project or ticket scope) is refused as the newPROJECT_REMOVED. A due schedule is recorded as "Skipped — The project was removed from Volli." in the project's own history.board.snapshotandboard.rosteranswer "Unknown project". Every write throughrecordedis refused with "This project was removed from Volli. Add its folder again to change it." A write the board already took still replays.servedWorkspaceanswersnull, which closes every Workspace door for a removed project.readTicketreads the ticket as gone.MCP credentials:
forgetRemovedServersdrops the credential of any server whose row is gone or whose project is detached. The server rows stay for a re-add. The servers' in-flight sign-ins were already cancelled by #920'sdisconnectProject.Dialog text is now: "The folder and its tickets and Sessions are kept. Add the folder again to bring them back."
Export carries
removedAt.Decisions
PROJECT_REMOVEDand recorded as a skip in the project's own Run history. I did not filter the scheduler: a filtered schedule stays unevaluated after a re-add until something refreshes the scheduler, and its missed occurrences would later be reported as "not observed". Consecutive refusals are not coalesced.ownedContainers, the allow-list every destructive worktree path checks, so no sweep, trim or orphan delete reaches its checkouts.removed_at, so it shows a detached project on its rail as an ordinary one. Nothing is lost. If a new project took a removed project's prefix, that older build shows two projects with that prefix. The min-reader floor is unchanged (58).removeProjecton an id that is not on the rail (unknown or already removed) is a no-op.deleteProjectstays as it was. No product door calls it any more; its cascade test still documents the schema.Not in this PR
ticket.move(executeTicketMove) does not pass through the board'srecordedgate. No door can name a removed project's ticket: CLI resolution sees only projects on the rail, and the renderer has forgotten the project.Open question
cloudon, the renderer follows the board feed (boardProtocol()inlib/boot.ts). Removal disposes the project's feed. On a re-add,seedProjectopens the protocol sync again, andboard.snapshotanswers again because the project is back on the rail. I have not verified in a live app that the re-opened feed carries later changes for that Workspace. Flag-off, which every stable build runs, reads the board through bootstrap and roster, and that path is covered by thedata-ipctest.Review (READY WITH NITS at
3540ae7fc)servedWorkspace, the PTY scope (ticket and Board terminals) and the board commands (snapshot, roster, and every write throughrecorded);ticket.move(see above).PROJECT_REMOVEDcode (sharedAutomationRunRefusalCode, not in the protocol schema), and the sentence "The project was removed from Volli." It is refused before the ledger accepts the Run, so no durable command stores the code. Coalescing consecutive refusals: not done.findProjectByPathnow documents its exact-string match: a different spelling of the same folder registers a second project, as it always has.ownedContainersleaving out a removed project's container, and for a removed project's due schedule end to end over the real Run door.claimed) with "This folder belongs to Old, which was removed. Add it again instead." instead of failing on the UNIQUE constraint.Tests
Red first: with #920's
project-remove.ts(delete) restored over this branch,project-remove.test.tsfailed 5 of 12, including "keeps the project's tickets and Sessions, and only hides it from the rail" (rowundefined, cascade). It passes with the change.Run locally, one file at a time with
--maxWorkers=1, all passing:project-remove: detach order step by step with what the database held, history kept, wall-clock default, no-op for a removed id, and end to end overhostProjectLiveWork(no binding left, Sessions kept).project-create: re-attach keeps id, counter, tickets and Sessions with no detection and one announcement; preview; a concurrent double add; refused on a taken prefix or a missing folder.db/projects-repo: detach and reattach semantics, listings, count scopes, id and path lookups.mcp/settings: credentials forgotten for a detached project's servers and for a missing row, another project's kept, server row kept.session-runtime/location: prepare and reaffirm refused while detached, resolve answers, both allowed again after a re-attach.automation-services:findProjectand the armedreadTicketrespect detach.operator-door:volli project addpreviews and re-attaches.handlers/host-handlers:removed_atstamped at the host clock.db/export,db/migrations-lock,db/migrations.db/migrations-authority-collision: CI caught this one. The 053 collision repair is now judged at 054, where it ends; the head check expects only 064's added NULL column.board/commands,pty/scope,automations/runandproject-relink;worktree/containersandautomation-services.headless(the real scheduler recordsPROJECT_REMOVED);project-create: a new project is refused a removed project's prefix;db/projects-repo,automation-servicesandoperator-door.host-protocol:servedWorkspaceanswers only while the project is on the rail.main/data-ipc(217): removal hides the project from bootstrap; re-adding the folder brings back the project and its ticket and broadcasts{ projectId }.Other checks:
vp checkon every touched file: pass.pnpm check:protocol-schema --base origin/main: pass.node scripts/lock-migrations.mjs: current. Migration 064 is not claimed by any open PR.Not run locally (the machine is shared): package typecheck, coverage, smokes. CI is the proof.
CI gatewas green on3540ae7fcwith no reruns. The review round's head is2caa84b34.CI rerun (disclosed)
Run 39253523302 on
2caa84b34failed two jobs outside this diff. With approval, the failed jobs were rerun once:automations-picker-smoketimed out waiting for "Move only" to light up during the drag, at check 6 and then check 8 on its retry. This is the known flake VC-844.packages/agent-runtime/src/pi/models.test.ts > PiFileCredentialStore caching > does not join a shared pass that a write has already overtakenhit "Test timed out in 5000ms" (job 118062947625). This is a new flake, to be filed. The PR does not touchagent-runtime, and the same job passed on3540ae7fc.On the one rerun, both jobs passed, including that test.
CI gateis green on2caa84b34.Overlap
git merge-treeis clean againstorigin/mainand the heads of #909, #919, #920, #921 and #922, plus #633, #928 and #933. #651 and #665 already conflict withmainon the same files without this change.