import Foundation
import Testing
@testable import Kanban

/// The write-provenance ledger — 02-architecture.md ▸ Components ▸ EchoLedger:
///
/// > Every BoardWriter operation drops a receipt of its expected on-disk outcome before returning:
/// > path → content hash for writes …, an absence marker for deletes, an old→new pair for folder
/// > moves; a newer app write to the same path supersedes the receipt. Classification runs per
/// > observed changed file in a debounce window: current on-disk content matches the receipt →
/// > app-mediated, receipt consumed; no receipt, or mismatch → foreign. … Feeds attribution and
/// > announcements only — never the render path.
///
/// Three layers, three suites: the receipts as values, the Writer actually dropping them, and the
/// reload seam turning them into silence or speech.

// MARK: - Fixtures

private let lane1 = Ident.lane1
private let lane2 = Ident.lane2
private let card1 = Ident.card1
private let card2 = Ident.card2

private let lane1ID = ItemID(rawValue: lane1)
private let card1ID = ItemID(rawValue: card1)
private let card2ID = ItemID(rawValue: card2)

/// Two lanes — Todo (2 cards), Doing (empty).
private func makeBoard() throws -> WriterFixture {
    let fixture = try WriterFixture()
    try fixture.board()
    try fixture.lane(lane1, order: "1024", title: "Todo")
    try fixture.lane(lane2, order: "2048", title: "Doing")
    try fixture.card(card1, in: lane1, order: "1024", title: "Fix login")
    try fixture.card(card2, in: lane1, order: "2048", title: "Second")
    return fixture
}

// MARK: - The receipts themselves

/// Pure, over synthetic paths: none of these rules needs a filesystem, and stating them without one
/// is what makes them readable as the design's own sentences.
@Suite("EchoLedger — receipts")
struct EchoLedgerReceiptTests {

    private let index = "/b/lane/card/index.md"
    private let folder = "/b/lane/card"

    @Test("A write's receipt is its content hash, at its path")
    func writeDropsAContentHash() {
        let ledger = EchoLedger()
        ledger.recordWrite(atPath: index, hash: EchoLedger.hash(of: "one"))

        #expect(ledger.receipt(atPath: index) == .content(hash: EchoLedger.hash(of: "one")))
    }

    /// "A newer app write to the same path supersedes the receipt" — only the final content decides,
    /// so the intermediate is not kept and cannot be redeemed.
    @Test("A newer write to the same path supersedes the earlier receipt")
    func supersession() {
        let ledger = EchoLedger()
        ledger.recordWrite(atPath: index, hash: EchoLedger.hash(of: "one"))
        ledger.recordWrite(atPath: index, hash: EchoLedger.hash(of: "two"))

        #expect(ledger.outstandingReceipts == 1, "one path, one receipt — never a journal")
        #expect(ledger.classify([index: .content(hash: EchoLedger.hash(of: "one"))]) == .foreign)

        ledger.recordWrite(atPath: index, hash: EchoLedger.hash(of: "two"))
        #expect(ledger.classify([index: .content(hash: EchoLedger.hash(of: "two"))]) == .appMediated)
    }

    @Test("A delete drops an absence marker, satisfied by nothing being there")
    func deleteDropsAnAbsenceMarker() {
        let ledger = EchoLedger()
        ledger.recordDeletion(atPath: folder)

        #expect(ledger.receipt(atPath: folder) == .absence)
        #expect(ledger.classify([folder: .present]) == .foreign, "something is there — nobody vouched for that")
        #expect(ledger.classify([folder: .absent]) == .appMediated)
    }

    /// A folder that is gone cannot have an `index.md` whose hash still means anything, and leaving
    /// those receipts behind would let the next thing to appear at that path inherit them.
    @Test("A delete takes the receipts below it with it")
    func deleteSweepsItsSubtree() {
        let ledger = EchoLedger()
        ledger.recordWrite(atPath: index, hash: EchoLedger.hash(of: "one"))
        ledger.recordDeletion(atPath: folder)

        #expect(ledger.receipt(atPath: index) == nil)
        #expect(ledger.outstandingReceipts == 1)
    }

    /// "An old→new pair for folder moves" — one fact, filed at both ends so whichever end a reload
    /// observes finds it.
    @Test("A move files the same pair at both ends")
    func moveDropsAPair() {
        let ledger = EchoLedger()
        ledger.recordMove(fromPath: "/b/lane/card", toPath: "/b/.trash/card")

        let pair = EchoLedger.Receipt.move(from: "/b/lane/card", to: "/b/.trash/card")
        #expect(ledger.receipt(atPath: "/b/lane/card") == pair)
        #expect(ledger.receipt(atPath: "/b/.trash/card") == pair)
    }

    @Test("The pair reads correctly from either end — gone here, arrived there")
    func moveIsSatisfiedFromEitherEnd() {
        let ledger = EchoLedger()
        ledger.recordMove(fromPath: "/b/lane/card", toPath: "/b/.trash/card")
        #expect(ledger.classify(["/b/lane/card": .absent]) == .appMediated, "the board side sees a departure")

        let other = EchoLedger()
        other.recordMove(fromPath: "/b/lane/card", toPath: "/b/.trash/card")
        #expect(other.classify(["/b/.trash/card": .present]) == .appMediated, "the shown-trash side sees an arrival")
    }

    @Test("Reading either end of a pair retires both — half a move is never left behind")
    func consumingOneEndRetiresBoth() {
        let ledger = EchoLedger()
        ledger.recordMove(fromPath: "/b/lane/card", toPath: "/b/.trash/card")

        #expect(ledger.classify(["/b/lane/card": .absent]) == .appMediated)
        #expect(ledger.outstandingReceipts == 0)
    }

    /// The bytes did not change, only where they are — so an `index.md` receipt from earlier in the
    /// same bracket is still the truth about the file that just travelled.
    @Test("A move rebases the receipts below it rather than dropping them")
    func moveRebasesItsSubtree() {
        let ledger = EchoLedger()
        ledger.recordWrite(atPath: "/b/lane/card/index.md", hash: EchoLedger.hash(of: "one"))
        ledger.recordMove(fromPath: "/b/lane/card", toPath: "/b/.trash/card")

        #expect(ledger.receipt(atPath: "/b/lane/card/index.md") == nil)
        #expect(ledger.receipt(atPath: "/b/.trash/card/index.md") == .content(hash: EchoLedger.hash(of: "one")))
    }

    // MARK: Classification

    /// The launch-catch-up doctrine, and the reconciling reload's whole story: "the app never
    /// vouches for changes it didn't witness".
    @Test("No receipt is foreign — an empty ledger vouches for nothing")
    func noReceiptIsForeign() {
        #expect(EchoLedger().classify([index: .content(hash: EchoLedger.hash(of: "anything"))]) == .foreign)
    }

    @Test("A match consumes the receipt — one write, one echo")
    func matchConsumes() {
        let ledger = EchoLedger()
        ledger.recordWrite(atPath: index, hash: EchoLedger.hash(of: "one"))

        #expect(ledger.classify([index: .content(hash: EchoLedger.hash(of: "one"))]) == .appMediated)
        #expect(ledger.outstandingReceipts == 0)
        #expect(
            ledger.classify([index: .content(hash: EchoLedger.hash(of: "one"))]) == .foreign,
            "a second reload observing the same file finds nothing vouching for it"
        )
    }

    /// The conservative direction: keeping an unsatisfied receipt makes the next observation of the
    /// item foreign too, and a receipt later satisfied again is exactly the byte-identical race the
    /// design already accepts.
    @Test("A mismatch keeps the receipt rather than spending it")
    func mismatchKeepsTheReceipt() {
        let ledger = EchoLedger()
        ledger.recordWrite(atPath: index, hash: EchoLedger.hash(of: "one"))

        #expect(ledger.classify([index: .content(hash: EchoLedger.hash(of: "other"))]) == .foreign)
        #expect(ledger.outstandingReceipts == 1)
    }

    /// **Every** held receipt has to still match. An item whose `index.md` the app wrote and whose
    /// attachment somebody else removed is not the app's echo.
    @Test("One unsatisfied receipt in a footprint makes the whole item foreign")
    func oneMismatchDecides() {
        let ledger = EchoLedger()
        ledger.recordWrite(atPath: index, hash: EchoLedger.hash(of: "one"))
        ledger.recordWrite(atPath: folder + "/attachments/a.png", hash: EchoLedger.hash(of: "bytes"))

        #expect(
            ledger.classify([
                index: .content(hash: EchoLedger.hash(of: "one")),
                folder + "/attachments/a.png": .absent
            ]) == .foreign
        )
    }

    /// The snapshot names a card's attachments but never reads their bytes, so an attachment receipt
    /// can only ever be checked for arrival — which is what keeps classification free of I/O.
    @Test("A content receipt for bytes the reload never read is satisfied by arrival")
    func unreadBytesAreSatisfiedByPresence() {
        let ledger = EchoLedger()
        ledger.recordWrite(atPath: folder + "/attachments/a.png", hash: EchoLedger.hash(of: "bytes"))

        #expect(ledger.classify([folder + "/attachments/a.png": .present]) == .appMediated)
    }

    @Test("A path the ledger knows nothing about is not consulted — a copy's carried files are not foreign")
    func unknownPathsAreNotDemanded() {
        let ledger = EchoLedger()
        ledger.recordWrite(atPath: index, hash: EchoLedger.hash(of: "one"))

        #expect(
            ledger.classify([
                index: .content(hash: EchoLedger.hash(of: "one")),
                folder + "/attachments/carried.png": .present
            ]) == .appMediated
        )
    }

    // MARK: The two races

    /// "An agent writing byte-identical bytes over a fresh app write matches and classifies
    /// app-mediated — with identical bytes the misattribution is unobservable in the tree, accepted."
    @Test("A byte-identical foreign overwrite of a fresh app write classifies app-mediated")
    func byteIdenticalOverwriteIsAppMediated() {
        let ledger = EchoLedger()
        let landed = "---\nschema: 1\ntitle: Todo\norder: 1024\n---\n\n"
        ledger.recordWrite(atPath: index, hash: EchoLedger.hash(of: landed))

        // Somebody else rewrote the file — with exactly the same bytes.
        #expect(ledger.classify([index: .content(hash: EchoLedger.hash(of: landed))]) == .appMediated)
    }

    /// "A foreign edit landing on an app-written path inside the same window misses the hash and the
    /// file classifies foreign — last writer wins the file, the app's subsumed intermediate never
    /// separately recorded."
    @Test("A foreign edit over a fresh app write classifies foreign — final content decides")
    func foreignEditOverAFreshWriteIsForeign() {
        let ledger = EchoLedger()
        ledger.recordWrite(atPath: index, hash: EchoLedger.hash(of: "---\nschema: 1\ntitle: Todo\n---\n\n"))

        let theirs = "---\nschema: 1\ntitle: Their title\n---\n\n"
        #expect(ledger.classify([index: .content(hash: EchoLedger.hash(of: theirs))]) == .foreign)
    }
}

// MARK: - The Writer's drops

/// The receipts are dropped **inside `BoardWriter`**, at the primitives that touch disk, into
/// whichever ledger `EchoLedger.current` is bound to — so these run the real Writer over a real temp
/// board with a ledger bound by hand, exactly as `BoardStore.performWrite` binds one.
@Suite("EchoLedger — what the Writer drops")
struct EchoLedgerWriterTests {

    @Test("Every index.md write drops a receipt for the bytes that landed")
    func indexWritesDropReceipts() throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let ledger = EchoLedger()
        let folder = fixture.url(lane1)

        try EchoLedger.$current.withValue(ledger) {
            try BoardWriter.updateIndex(inItemFolder: folder, operation: .style(title: nil)) { document in
                document.set(FrontmatterKeys.width, to: .int(3))
            }
        }

        let landed = try fixture.indexText(lane1)
        #expect(
            ledger.receipt(at: folder.appendingPathComponent("index.md"))
                == .content(hash: EchoLedger.hash(of: landed))
        )
    }

    @Test("A delete-to-trash drops the old→new pair")
    func deleteToTrashDropsAPair() throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let ledger = EchoLedger()
        let from = fixture.url("\(lane1)/\(card1)")

        try EchoLedger.$current.withValue(ledger) {
            _ = try BoardWriter.deleteCardToTrash(at: from, inBoard: fixture.root)
        }

        let to = fixture.url(".trash/\(card1)")
        #expect(ledger.receipt(at: from) == .move(from: EchoLedger.key(from), to: EchoLedger.key(to)))
    }

    @Test("A purge drops an absence marker")
    func purgeDropsAnAbsenceMarker() throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let ledger = EchoLedger()
        let folder = fixture.url("\(lane1)/\(card1)")

        try EchoLedger.$current.withValue(ledger) {
            try BoardWriter.purgeItem(at: folder)
        }

        #expect(ledger.receipt(at: folder) == .absence)
    }

    /// "Attachment imports hash during the copy (the bytes stream through the app anyway)" — see
    /// `EchoLedger.recordImport(at:)` for where that phrase and `FileManager.copyItem` do not quite
    /// meet, and why the hash is taken from the landed file instead.
    @Test("An attachment import hashes what it copied")
    func attachmentImportHashes() throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let ledger = EchoLedger()
        let payload = Data("not a real png, but real bytes".utf8)
        let source = try fixture.file("sources/photo.png", payload)
        let cardFolder = fixture.url("\(lane1)/\(card1)")

        try EchoLedger.$current.withValue(ledger) {
            _ = try BoardWriter.importAttachments([source], intoCard: cardFolder)
        }

        let landed = cardFolder.appendingPathComponent("attachments/photo.png")
        #expect(ledger.receipt(at: landed) == .content(hash: EchoLedger.hash(of: payload)))
    }

    /// The seam is the bracket, not the process: a Writer call outside one — another board's tree,
    /// a template instantiation, a test — records nothing, because there is no session whose echo
    /// it would be.
    @Test("A Writer call outside a bracket records nothing")
    func noLedgerNoReceipts() throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }

        try BoardWriter.updateIndex(inItemFolder: fixture.url(lane1), operation: .style(title: nil)) { document in
            document.set(FrontmatterKeys.width, to: .int(3))
        }

        #expect(EchoLedger.current == nil)
    }
}

// MARK: - At the reload seam

/// The whole round trip: a write goes out through `BoardStore.performWrite`, comes back through a
/// reload, and the ledger is what decides whether the board says anything about it.
@MainActor
@Suite("EchoLedger — at the reload seam")
struct EchoLedgerStoreTests {

    @MainActor
    private final class SpokenLog {
        var lines: [String] = []
        func record(_ phrase: String?) {
            if let phrase { lines.append(phrase) }
        }
    }

    private func listen(to store: BoardStore) -> SpokenLog {
        let log = SpokenLog()
        store.announce = { log.record($0) }
        return log
    }

    private func reload(_ store: BoardStore, _ origin: WatchOrigin = .foreign) async {
        store.handleWatcherEvent(.treeChanged(origin))
        await store.awaitQuiescence()
    }

    // MARK: The one-way flow's invariant

    /// **Feeds attribution and announcements only — never the render path** (02-architecture.md ▸
    /// Layering, and the whole reason files-are-truth survives an app that also writes them).
    ///
    /// The ledger is poisoned with receipts that describe a board that does not exist, and the
    /// reload lands anyway, byte for byte what the loader read — because the loader had already
    /// finished before a single receipt was consulted, and consulting them cannot reach back.
    @Test("A poisoned ledger cannot move the snapshot by one byte")
    func theLedgerNeverFeedsTheRenderPath() async throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let store = try BoardStore(rootURL: fixture.root)

        for relative in ["", lane1, lane2, "\(lane1)/\(card1)", "\(lane1)/\(card2)"] {
            let folder = fixture.url(relative)
            store.echoes.recordWrite(at: folder.appendingPathComponent("index.md"), text: "a board that isn't there")
            store.echoes.recordDeletion(at: folder.appendingPathComponent("attachments"))
        }

        try fixture.card(card1, in: lane1, order: "1024", title: "Changed by somebody")
        await reload(store)

        #expect(store.snapshot == (try fixture.snapshot()), "the snapshot is the loader's, and only the loader's")
        #expect(store.snapshot.lanes.first?.cards.first?.title.value == "Changed by somebody")
    }

    // MARK: App-mediated writes are silent, file by file

    @Test("A card created through the store is not news")
    func appCreateIsSilent() async throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let store = try BoardStore(rootURL: fixture.root)
        let log = listen(to: store)

        store.transient.beginPlaceholder(inLane: lane1ID)
        store.transient.updateDraft("Minted by the app")
        _ = store.commitPlaceholder()
        await reload(store, .appMediated)

        #expect(store.snapshot.lanes.first?.cards.count == 3, "the card really arrived")
        #expect(log.lines.isEmpty)
    }

    @Test("A card moved between lanes through the store is not news")
    func appMoveIsSilent() async throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let store = try BoardStore(rootURL: fixture.root)
        let log = listen(to: store)

        store.moveCards([card1ID], toLane: ItemID(rawValue: lane2), at: 0)
        await reload(store, .appMediated)

        #expect(store.snapshot.lanes.last?.cards.map(\.id) == [card1ID], "the move really landed")
        #expect(log.lines.isEmpty)
    }

    /// The attachment path, whose receipt is checked for arrival rather than for bytes: the card's
    /// rendered content changed (its attachment listing did), and the ledger still vouches for it.
    @Test("An attachment imported through the store is not news")
    func appAttachmentImportIsSilent() async throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let store = try BoardStore(rootURL: fixture.root)
        let source = try fixture.file("sources/photo.png", Data("bytes".utf8))
        let log = listen(to: store)

        store.importAttachments([source], toCard: card1ID)
        await reload(store, .appMediated)

        #expect(store.snapshot.lanes.first?.cards.first?.attachments == ["photo.png"], "the import really landed")
        #expect(log.lines.isEmpty)
    }

    @Test("A board renamed through the store is not news")
    func appBoardRenameIsSilent() async throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let store = try BoardStore(rootURL: fixture.root)
        let log = listen(to: store)

        store.renameBoard("Renamed by the app")
        await reload(store, .appMediated)

        #expect(store.snapshot.title.value == "Renamed by the app")
        #expect(log.lines.isEmpty)
    }

    // MARK: Foreign writes announce, whatever the reload calls itself

    @Test("The same rename made by somebody else is news on any origin")
    func foreignBoardRenameSpeaks() async throws {
        for origin in [WatchOrigin.foreign, .appMediated, .reconciling] {
            let fixture = try makeBoard()
            defer { fixture.tearDown() }
            let store = try BoardStore(rootURL: fixture.root)
            let log = listen(to: store)

            try fixture.board(title: "Renamed by an agent")
            await reload(store, origin)

            #expect(log.lines == ["Board changed"], "origin \(origin.rawValue)")
        }
    }

    /// The mixed reload the ruling's "per-file facts" phrase exists for: one write of ours, one of
    /// theirs, landing in the same debounce window. Only theirs is counted.
    @Test("A mixed window announces only the changes nobody vouched for")
    func aMixedWindowCountsOnlyTheForeignHalf() async throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let store = try BoardStore(rootURL: fixture.root)
        let log = listen(to: store)

        store.setLaneWidth(lane1ID, units: 3)
        try fixture.card(card2, in: lane1, order: "2048", title: "Theirs")
        await reload(store, .appMediated)

        #expect(log.lines == ["Board changed: 1 card edited"], "one card, not one card and one lane")
    }

    // MARK: The two races, end to end

    /// The receipt is checked against the bytes the walk actually read, so an agent that rewrote the
    /// file with the app's own bytes is indistinguishable from the app — "with identical bytes the
    /// misattribution is unobservable in the tree, accepted".
    @Test("A byte-identical foreign overwrite of a fresh app write stays silent")
    func byteIdenticalOverwriteStaysSilent() async throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let store = try BoardStore(rootURL: fixture.root)
        let log = listen(to: store)

        store.setLaneWidth(lane1ID, units: 3)
        // Somebody else rewrites the file — with exactly the bytes the app just put there.
        try fixture.item(lane1, try fixture.indexText(lane1))
        await reload(store, .appMediated)

        #expect(store.snapshot.lanes.first?.width.value == 3, "the change is real and observable")
        #expect(log.lines.isEmpty)
    }

    /// The other side of the same window: a foreign edit that lands on the app-written path misses
    /// the hash, so the file classifies foreign — last writer wins the file.
    @Test("A foreign edit over a fresh app write announces")
    func foreignEditOverAFreshWriteSpeaks() async throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let store = try BoardStore(rootURL: fixture.root)
        let log = listen(to: store)

        store.setLaneWidth(lane1ID, units: 3)
        try fixture.lane(lane1, order: "1024", title: "Renamed by an agent", width: 5)
        await reload(store, .appMediated)

        #expect(store.snapshot.lanes.first?.title.value == "Renamed by an agent", "last writer won the file")
        #expect(log.lines == ["Board changed: 1 lane edited"])
    }
}

// MARK: - Heal-marked receipts

/// **The Writer's heal operations drop heal-marked receipts** (06-history-undo.md ▸ Commit messages,
/// ruled 2026-07-29: "attribution machinery like the author split, never message tagging").
///
/// **Inert beyond the ledger itself**: nothing here reads the flag and nothing renders it — it is
/// what whatever records history next (the foreign-change journal, `strategy/01-git-excision.md` ▸
/// Successors) would read to split a heal's paths out from a user's own, and these tests pin the
/// seam it will read from, not a consumer that does not exist yet.
@Suite("EchoLedger — heal-marked receipts")
struct EchoLedgerHealMarkTests {

    /// An ordinary gesture is not a heal, and says so by default.
    @Test("An ordinary write is not heal-marked")
    func ordinaryWritesAreNotHeals() throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let ledger = EchoLedger()
        let folder = fixture.url(lane1)

        try EchoLedger.$current.withValue(ledger) {
            try BoardWriter.updateIndex(inItemFolder: folder, operation: .style(title: nil)) { document in
                document.set(FrontmatterKeys.width, to: .int(3))
            }
        }

        #expect(!ledger.isHeal(at: folder.appendingPathComponent("index.md")))
    }

    /// The loose-file relocation — an app-initiated heal, so its landed file's receipt is marked.
    @Test("The loose-file relocation marks what it moved")
    func relocationMarksItsMoves() throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let ledger = EchoLedger()
        let cardFolder = fixture.url("\(lane1)/\(card1)")
        try fixture.file("\(lane1)/\(card1)/notes.txt", Data("notes".utf8))

        try EchoLedger.$current.withValue(ledger) {
            _ = try BoardWriter.relocateLooseFiles(["notes.txt"], inCard: cardFolder)
        }

        #expect(ledger.isHeal(at: cardFolder.appendingPathComponent("attachments/notes.txt")))
    }

    /// **The import-boundary normalization is not marked**, and the distinction is the design's:
    /// an *inline* heal batches with the gesture that triggered it, so its paths belong in that
    /// gesture's commit rather than in a heal's own.
    @Test("The paste boundary's normalization is not heal-marked")
    func inlineNormalizationIsNotAHeal() throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let ledger = EchoLedger()
        let cardFolder = fixture.url("\(lane1)/\(card1)")
        try fixture.file("\(lane1)/\(card1)/notes.txt", Data("notes".utf8))

        try EchoLedger.$current.withValue(ledger) {
            _ = try BoardWriter.normalizeLooseFiles(inCard: cardFolder)
        }

        #expect(!ledger.isHeal(at: cardFolder.appendingPathComponent("attachments/notes.txt")))
    }

    /// The legacy-tombstone migration marks both halves of what it wrote — and the ordinary delete
    /// it shares a body with does not, because that one is a gesture.
    @Test("The tombstone migration marks, the delete beside it does not")
    func migrationMarksButDeleteDoesNot() throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let ledger = EchoLedger()
        try fixture.item(
            "\(lane1)/\(Ident.card3)",
            "---\nschema: 1\norder: 4096\ntitle: Tombstoned\ndeleted: 2026-01-01T00:00:00Z\n---\nbody\n"
        )

        try EchoLedger.$current.withValue(ledger) {
            _ = try BoardWriter.migrateTombstonedCard(
                at: fixture.url("\(lane1)/\(Ident.card3)"),
                inBoard: fixture.root
            )
            _ = try BoardWriter.deleteCardToTrash(
                at: fixture.url("\(lane1)/\(card1)"),
                inBoard: fixture.root
            )
        }

        let migrated = fixture.url(".trash/\(Ident.card3)")
        #expect(ledger.isHeal(at: migrated))
        #expect(ledger.isHeal(at: migrated.appendingPathComponent("index.md")))
        let deleted = fixture.url(".trash/\(card1)")
        #expect(!ledger.isHeal(at: deleted))
        #expect(!ledger.isHeal(at: deleted.appendingPathComponent("index.md")))
    }

    /// The agent guide's write, and the claimed-name displacement — both app-initiated, both marked.
    @Test("The guide write and a displacement are heals")
    func guideAndDisplacementAreHeals() throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let ledger = EchoLedger()
        try Data("squatter".utf8).write(to: fixture.root.appendingPathComponent(".trash"))

        try EchoLedger.$current.withValue(ledger) {
            _ = try BoardWriter.displaceClaimedName(
                ClaimedNameSquatter(name: ".trash", found: .file, expected: .directory),
                atBoardRoot: fixture.root
            )
            _ = try AgentGuide.install(atBoardRoot: fixture.root)
        }

        #expect(ledger.isHeal(at: fixture.root.appendingPathComponent(".trash 2")))
        #expect(ledger.isHeal(at: fixture.root.appendingPathComponent(AgentGuide.filename)))
    }

    /// **Supersession drops the mark with the receipt it belonged to**: a later ordinary write to a
    /// healed path is exactly the case where the path stops being the heal's alone.
    @Test("An ordinary write over a healed path clears the mark")
    func supersessionClearsTheMark() throws {
        let fixture = try makeBoard()
        defer { fixture.tearDown() }
        let ledger = EchoLedger()
        let index = fixture.url(lane1).appendingPathComponent("index.md")

        ledger.recordWrite(at: index, text: "one")
        ledger.markHeal(at: index)
        #expect(ledger.isHeal(at: index))

        ledger.recordWrite(at: index, text: "two")
        #expect(!ledger.isHeal(at: index))
        #expect(ledger.receipt(at: index) == .content(hash: EchoLedger.hash(of: "two")))
    }

    /// A mark with no receipt to attach to describes nothing, so it creates nothing.
    @Test("Marking an unknown path is a no-op")
    func markingAnUnknownPathIsANoOp() {
        let ledger = EchoLedger()
        ledger.markHeal(atPath: "/nowhere/index.md")
        #expect(ledger.receipt(atPath: "/nowhere/index.md") == nil)
        #expect(!ledger.isHeal(atPath: "/nowhere/index.md"))
        #expect(ledger.outstandingReceipts == 0)
    }

    /// A move is one fact under two keys, so marking either end marks both — the same rule that
    /// retires both ends when one is consumed.
    @Test("Marking one end of a move marks both")
    func markingAMoveMarksBothEnds() {
        let ledger = EchoLedger()
        ledger.recordMove(fromPath: "/b/lane/card", toPath: "/b/.trash/card")
        ledger.markHeal(atPath: "/b/.trash/card")

        #expect(ledger.isHeal(atPath: "/b/.trash/card"))
        #expect(ledger.isHeal(atPath: "/b/lane/card"))
    }

    /// The flag changes **nothing** about classification: a heal is an app write like any other, and
    /// the render path never consults the ledger at all.
    @Test("A heal mark does not change provenance")
    func markDoesNotChangeClassification() {
        let ledger = EchoLedger()
        let index = "/b/lane/index.md"
        ledger.recordWrite(atPath: index, hash: EchoLedger.hash(of: "one"))
        ledger.markHeal(atPath: index)

        #expect(ledger.classify([index: .content(hash: EchoLedger.hash(of: "one"))]) == .appMediated)
    }
}
