Deleting a patch is local-only, so a peer republishes it on the next sync #101

Open
opened 2026-08-14 07:53:43 +00:00 by a73x · 1 comment
a73x commented 2026-08-14 07:53:43 +00:00 (Migrated from git.a73x.sh)

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.


Migrated from git-collab issue b6542cec84c0c7aac3f5dbb4343055156005b51a.

Original author: a73x dev@a73x.sh.

`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. --- Migrated from git-collab issue `b6542cec84c0c7aac3f5dbb4343055156005b51a`. Original author: a73x <dev@a73x.sh>.
a73x commented 2026-08-14 07:56:31 +00:00 (Migrated from git.a73x.sh)

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.
**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.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
a73x/git-collab#101
No description provided.