Every comment-trash purge kneels to the ownership gate — the container-whole retirement retires

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
2026-08-06 17:10:09 -04:00
parent a60d97689e
commit ea15d1ac74
7 changed files with 449 additions and 55 deletions
+4 -2
View File
@@ -3084,8 +3084,10 @@ public enum WriteOperation: Sendable, Equatable, CustomStringConvertible {
/// note is the precedent one level up.
case deleteComment(title: String?)
/// `comments/.trash/` emptied at card-window close, and as the crash-residue sweep at the next
/// open (§ Enhanced schema).
/// `comments/.trash/` emptied whole the container-wide purge
/// (`BoardWriter.purgeCommentTrash(inCard:)`), which 13-native-undo.md retired from production on
/// 2026-08-06: both purge consumers now remove per entry behind the ownership gate, through
/// `purgeItem(at:)` and therefore under `.purge`. Kept with its primitive see that method.
///
/// **No payload, unlike its four siblings**, and for `.agentGuide`'s reason: this is bookkeeping
/// the app does on its own over a folder that is "never a UI surface", with one outcome nobody
+21 -7
View File
@@ -264,10 +264,26 @@ extension BoardWriter {
try moveComment(trashed, into: CommentThread.folder(inCard: cardFolder), operation: operation)
}
/// **Empties one card's `comments/.trash/`** at card-window close, and as the crash-residue
/// sweep at the next open (01-storage-format.md § Enhanced schema: "purged when the card window
/// closes (rides the close flush; crash residue sweeps at the next card-window open,
/// armed-then-cleared like every heal memo)").
/// **Empties one card's `comments/.trash/` whole** and **no production caller does that any
/// more** (13-native-undo.md Interaction with the trash, ruled 2026-08-06):
///
/// > "**Every purge of `comments/.trash/` is per-entry behind the ownership gate** (ruled
/// > 2026-08-06 the container-whole retirement purge retires) ... The container-whole purge
/// > assumed one owning step per card's comment trash, and two sessions over the same card broke
/// > it."
///
/// Both consumers the retirement/no-step-close purge and the open-time residue sweep now
/// filter by `HistoryProviding.backedContent` and remove per entry through `purgeItem(at:)`
/// (`BoardStore.purgeCommentTrash(inCard:)`, `BoardStore.sweepCommentTrashResidue(inCard:)`). A
/// primitive that empties the container cannot express the gate, so nothing above it may call this.
///
/// It is **kept rather than deleted**, deliberately and narrowly: `WriteOperation.purgeCommentTrash`
/// is the vocabulary's word for this work and owns a bespoke user-facing sentence
/// (`BannerCenter` "Couldn't tidy up deleted comments") plus rows in three exhaustive switches,
/// and retiring the case would be a ripple across files for no behavioural gain. Read this as
/// legacy: the shape the ruling retired, still exercised by `CommentWriteTests` so the case's
/// plumbing stays honest, and the home of the operation vocabulary the gated purge's per-entry
/// failures do not use.
///
/// `emptyTrash`'s rules, one level down and for its reasons:
///
@@ -278,9 +294,7 @@ extension BoardWriter {
/// removed stays removed.
/// - A card with no thread trash removes nothing and answers `[]`.
///
/// **It registers no undo step** the permanent-delete posture (13-native-undo.md), which is
/// also what makes the leftover comment steps on the board stack go stale and skip with a banner
/// rather than resurrect a folder that is gone.
/// **It registers no undo step** the permanent-delete posture (13-native-undo.md).
@discardableResult
public static func purgeCommentTrash(inCard cardFolder: URL) throws(BoardWriteError) -> [ItemID] {
let operation = WriteOperation.purgeCommentTrash