Deleting a patch is local-only, so a peer republishes it on the next sync
open by a73x
`patch delete` removes the local ref and nothing else. Any clone that still holds the patch republishes it on its next sync, and the deleting clone re-adopts it on the one after. Demonstrated accidentally, in production. A two-clone round trip left a test patch `4c718907` on the live server. I deleted it locally, the stale refs were deleted from the server by hand, and the tracker looked clean. It is now visible on https://git.a73x.sh — `4c718907 open Two-clone round trip test by bob` — because the second clone still had it and synced. ``` bob's clone: 2 refs (never deleted there) my clone: 2 refs (re-adopted) the server: 2 refs (re-pushed by bob) ``` **This is correct behaviour for an append-only DAG** — deletion is not a concept the model has, and a peer holding data cannot be told to forget it by someone else's local action. The problem is that the command is called `delete` and reports `Deleted patch <id>`, which reads as an assertion about the patch rather than about one clone's refs. The trap has teeth beyond tidiness: the obvious way to remove something published by mistake — a wrong file, a credential in a patch body — is `patch delete`, and it will appear to work and then quietly undo itself. Whoever does that will believe the removal succeeded. Wanted, in rough order of value: - **Say what it does.** `Deleted patch <id> from this repository. Other clones that still have it will republish it on their next sync.` The behaviour may be unfixable; the false impression is not. - Consider whether `--json` should say the same, since a script cannot read a caveat it was never given. - Decide whether a distributed removal is wanted at all. A tombstone event that peers honour is expressible — `BodyEdit`/delete already supersede text this way (`b8a55806`) — but it is a genuine design question, not a bug fix, and "the DAG is an audit trail" was the deliberate answer for comments. - At minimum, document it in the README beside the storage model, where the append-only property is already explained. Found by looking at the deployed site (5c62b43b). No amount of local measurement would have shown it — my clone reported clean at the time.
Comments
a73x
**A second resurrection path, separate from the peer.** Cleaning this up took two deletions, not one. After the patch was gone from my clone, from the server, and from bob's clone (deleted entirely), it was still held in **local sync staging**: ``` refs/collab/sync/a73x/patches/4c7189…/events refs/collab/sync/a73x/patches/4c7189…/rev/8314eb52… ``` Staging is scratch — `sync` fetches into `refs/collab/sync/<remote>/*`, reconciles, then `cleanup_sync_refs` removes it. But it survives a sync that does not reach cleanup, and today's syncs failed partway more than once (the refname conflicts during the layout migration). `git-collab refs` was reporting **357 sync refs** on this clone shortly before; after a clean sync it reports zero, which is what that namespace is supposed to look like at rest. So a deleted object can come back from a peer *or* from this clone's own leftover scratch. Only after deleting both did it stay gone across a sync (verified: local 0, server 0, still 0 after `git-collab sync`). The peer path is inherent to the model. **This one is not** — scratch that outlives the operation that created it, and can then re-adopt data the user removed, is a defect rather than a property. Worth handling separately from the naming question above: - `cleanup_sync_refs` should run even when a sync fails partway, or staging should be cleared at the *start* of a sync as well as the end. A failed sync leaving state that changes the next one's outcome is the surprising part. - `git-collab refs` already surfaces the count, which is how this was noticed — 357 sitting there was visible and meaningless until it wasn't.