Rm and RemoveAllWithPrefix leave revision files behind and take no file lock (erasure gap) #30
Labels
No labels
area/client
area/revisions
bug
clarity/ambiguous
clarity/clear
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
size/L
size/M
wontfix
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
basepeak-ai/workspace-provider#30
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
Deleting a whole workspace, or a prefix inside one, does not reliably delete the file revisions stored for it:
Rm(workspace delete): every provider removes the revision store best effort, with the error discarded:pkg/client/s3.go:71:_ = newS.revisionsProvider.RemoveAllWithPrefix(ctx, s.dir)pkg/client/directory.go:91, same_ =pkg/client/azure.go:125, same_ =A failure (throttling, a partial
DeleteObjects, a network error) leaves every old revision of every file behind, andRmstill reports success. Also check the S3 prefix argument: it passes the factory'ss.dir, not the workspace'sdir.RemoveAllWithPrefix(id, prefix)with a non-empty prefix deletes only the content objects under the prefix. The revisions of those files stay inrevisions/<dir>/…for good. CompareDeleteFile, which removes a file's revisions too.Neither path takes the per-file lock that #28 introduced (
fileLocks). A concurrent revisioned write to a file under the prefix can recreate a revision or itsPendingIDmarker after the delete has listed the prefix, which leaves an orphan.Why it matters
This is a data-retention / GDPR Art. 17 erasure gap. Revisions are full prior copies of user files. When a user or project is deleted, bpai deletes the workspace and expects the data gone. On S3, every SaaS tenant, the old versions of every document can survive in the bucket with nothing pointing at them, so no later sweep finds them either.
Fix
Rm: return the revision-store error (or retry, then return it) instead of discarding it. Also verify the S3 prefix.RemoveAllWithPrefix: also removerevisions/…/<prefix>/.revisions/<dir>/prefixes whose workspace no longer exists, run on the SaaS bucket(s).Where it was found
Review of #27 / #28. Related to basepeak-ai/bpai#1220. Compare the earlier GDPR erasure sweep in bpai.
Confirmed against the code: Rm discards the revisions-store error via
_ =in s3.go:86, directory.go:108, and azure.go:140, so a failed revision cleanup is silently reported as success. RemoveAllWithPrefix (s3.go:384, directory.go:415, azure.go:363) only deletes content objects/files under the given prefix and never touches the parallelrevisions/<dir>/...tree, unlike DeleteFile (directory.go:142, azure.go:176, s3.go:154) which explicitly calls getRevisionInfo/deleteAllRevisions (revisions.go:14,134) per file. Neither Rm nor RemoveAllWithPrefix acquire fileLocks (filelock.go) before listing/deleting, so a concurrent revisioned write (revisionwrite.go) can recreate a revision or PendingID marker after the delete has already listed the prefix. One sub-claim needs correction before implementing: the S3 Rm prefix arguments.dir(s3.go:86) is always empty in practice, since Rm is only ever invoked on the top-level factory via Client.Rm/getFactory (client.go:146-157), and RemoveAllWithPrefix's empty-prefix branch already resolves to the correctrevisions/<dir>/path — passing the workspace'sdirinstead, as the issue suggests, would double-nest the prefix and break it, so the actual S3 fix is 'keep passing empty but document why', not swap the variable. The core fix (propagate the error, extend RemoveAllWithPrefix to also sweep the revisions subtree, add locking, add tests) is clear and well-localized to pkg/client, but two things are genuine product/design decisions the code can't answer.Questions before this can start: