import Foundation
import Testing
@testable import Kanban

/// **The integrity service's rules** (01-storage-format.md § Validation and healing, settled
/// 2026-07-29; 02-architecture.md ▸ Components ▸ IntegrityRules): the one vocabulary of object
/// validity, pinned as the pure functions it is.
///
/// The *scheduling* of heals lives next door (`HealSchedulerTests`), and each heal's own end-to-end
/// behavior stays in the suite that always owned it (`LooseFileRelocationTests`, `TrashStorageTests`,
/// `AgentGuideTests`). What is here is the consolidation itself: that the identity predicate, the
/// canonical form, the reserved-name tables, per-kind validation, the trash discriminator, the
/// on-touch heals and the defect vocabulary each have exactly one implementation and behave as the
/// design states them.

// MARK: - The identity predicate and its canonical form

@Suite("IntegrityRules ▸ identity")
struct IntegrityIdentityTests {

    /// Shape-only: hex, `8-4-4-4-12`, **any case, any version** — lowercase v4 is the app's emission
    /// rule, not the gate.
    @Test("The predicate is shape-only — any case, any version")
    func predicateIsShapeOnly() {
        #expect(IntegrityRules.isIdentityShaped("11111111-1111-4111-8111-111111111111"))
        #expect(IntegrityRules.isIdentityShaped("11111111-1111-4111-8111-111111111111".uppercased()))
        // v7's version nibble, and a variant nibble RFC 4122 would reject — both accepted.
        #expect(IntegrityRules.isIdentityShaped("0195a3f0-0000-7000-0000-000000000000"))
        #expect(!IntegrityRules.isIdentityShaped("notes"))
        #expect(!IntegrityRules.isIdentityShaped("11111111-1111-4111-8111-11111111111"))
        #expect(!IntegrityRules.isIdentityShaped("11111111111141118111111111111111"))
        #expect(!IntegrityRules.isIdentityShaped("gggggggg-1111-4111-8111-111111111111"))
    }

    /// **One predicate, one implementation** — the loader's spelling forwards to it, which is what
    /// "no parallel derivation" means in practice.
    @Test("The loader's gate is this predicate")
    func loaderSharesThePredicate() {
        for name in ["11111111-1111-4111-8111-111111111111", "NOTES", "", ".trash"] {
            #expect(BoardLoader.isUUIDShaped(name) == IntegrityRules.isIdentityShaped(name))
        }
    }

    /// **The fold** (settled 2026-07-29): `ItemID`'s equality and the Writer's string-level checks
    /// canonicalize through the same function. Before it, the Writer carried a private copy — one
    /// line, and one line too many for a rule that decides whether two folders are the same item.
    @Test("ItemID's equality is this canonical form")
    func itemIDSharesTheCanonicalForm() {
        let lower = "55555555-5555-4555-8555-555555555555"
        #expect(IntegrityRules.canonicalIdentity(lower.uppercased()) == lower)
        #expect(ItemID(rawValue: lower) == ItemID(rawValue: lower.uppercased()))
        #expect(ItemID(rawValue: lower).hashValue == ItemID(rawValue: lower.uppercased()).hashValue)
        // rawValue still round-trips byte-perfect — canonicalization is for *comparing*, never for
        // storing.
        #expect(ItemID(rawValue: lower.uppercased()).rawValue == lower.uppercased())
    }

    /// Total on anything: an off-shape name compares by its own lowercasing, the harmless reading.
    @Test("The canonical form is total")
    func canonicalFormIsTotal() {
        #expect(IntegrityRules.canonicalIdentity("Notes") == "notes")
        #expect(IntegrityRules.canonicalIdentity("") == "")
    }
}

// MARK: - The reserved-name tables

@Suite("IntegrityRules ▸ reserved names")
struct IntegrityReservedNameTests {

    /// One table, read by the loader under its own names.
    @Test("The tables are the loader's")
    func tablesAreShared() {
        #expect(BoardLoader.reservedCardChildNames == IntegrityRules.reservedCardChildNames)
        #expect(BoardLoader.reservedRootNames == IntegrityRules.claimedRootNameSet)
        #expect(BoardLoader.trashFolderName == IntegrityRules.trashFolderName)
        #expect(BoardWriter.attachmentsFolderName == IntegrityRules.attachmentsFolderName)
    }

    /// The claimed names carry **what kind of node each may be** — the fact the displacement heal
    /// turns on, and the reason the table is a list of values rather than a `Set<String>`.
    @Test("Each claimed name declares its node kind and whether it displaces")
    func claimedNamesDeclareTheirKind() throws {
        let trash = try #require(IntegrityRules.claimedRootNames.first { $0.name == ".trash" })
        #expect(trash.expected == .directory)
        #expect(trash.displacesSquatters)

        let guide = try #require(IntegrityRules.claimedRootNames.first { $0.name == "CLAUDE.md" })
        #expect(guide.expected == .file)
        #expect(guide.displacesSquatters)

        // The AGENTS.md twin (ruled 2026-08-09): claimed on the identical terms as CLAUDE.md.
        let agentsTwin = try #require(IntegrityRules.claimedRootNames.first { $0.name == "AGENTS.md" })
        #expect(agentsTwin.expected == .file)
        #expect(agentsTwin.displacesSquatters)

        // The standing exception: a rescue *destination* is never itself freed by a second
        // displacement, which would cascade renames (08-agent-integration.md ▸ Ownership).
        let userFile = try #require(IntegrityRules.claimedRootNames.first { $0.name == "CLAUDE.user.md" })
        #expect(!userFile.displacesSquatters)
        // Seeded once, then the user's to edit (06-history-undo.md ▸ Repository hygiene).
        let gitignore = try #require(IntegrityRules.claimedRootNames.first { $0.name == ".gitignore" })
        #expect(!gitignore.displacesSquatters)
    }

    /// `lstat` semantics: a **dangling** symlink is a node that is there.
    @Test("The node probe never follows a link")
    func nodeProbeUsesLstat() throws {
        let root = FileManager.default.temporaryDirectory
            .appendingPathComponent("IntegrityRulesTests-\(UUID().uuidString)", isDirectory: true)
        try FileManager.default.createDirectory(at: root, withIntermediateDirectories: true)
        defer { try? FileManager.default.removeItem(at: root) }

        try Data("x".utf8).write(to: root.appendingPathComponent("file.txt"))
        try FileManager.default.createDirectory(at: root.appendingPathComponent("dir"), withIntermediateDirectories: true)
        try FileManager.default.createSymbolicLink(
            atPath: root.appendingPathComponent("dangling").path,
            withDestinationPath: "nowhere"
        )
        try FileManager.default.createSymbolicLink(
            atPath: root.appendingPathComponent("toFile").path,
            withDestinationPath: "file.txt"
        )

        #expect(IntegrityRules.node(at: root.appendingPathComponent("file.txt")) == .file)
        #expect(IntegrityRules.node(at: root.appendingPathComponent("dir")) == .directory)
        #expect(IntegrityRules.node(at: root.appendingPathComponent("dangling")) == .symlink)
        #expect(IntegrityRules.node(at: root.appendingPathComponent("toFile")) == .symlink)
        #expect(IntegrityRules.node(at: root.appendingPathComponent("absent")) == nil)
    }
}

// MARK: - Placement and the trash discriminator

@Suite("IntegrityRules ▸ kind")
struct IntegrityKindTests {

    /// "Level is position", as two names and nothing else.
    @Test("Placement reads position")
    func placementReadsPosition() {
        let uuid = "11111111-1111-4111-8111-111111111111"
        #expect(IntegrityRules.placement(ofFolderNamed: uuid, inParentNamed: uuid) == .card)
        #expect(IntegrityRules.placement(ofFolderNamed: uuid, inParentNamed: "MyBoard.kanban") == .lane)
        #expect(IntegrityRules.placement(ofFolderNamed: uuid, inParentNamed: ".trash") == .insideTrash)
        // A board root's folder name is a Finder document name, so position cannot answer for it —
        // and neither can it for a hand-named folder. "Unknown" rather than "board" is what keeps a
        // guessed kind off disk.
        #expect(IntegrityRules.placement(ofFolderNamed: "MyBoard.kanban", inParentNamed: "Documents") == .unknown)
        #expect(IntegrityRules.placement(ofFolderNamed: "notes", inParentNamed: "MyBoard.kanban") == .unknown)
    }

    /// **The value is trusted outright** — no corroboration, no policing (re-ruled 2026-07-29).
    @Test("The trash discriminator trusts the value")
    func trashDiscriminatorTrustsTheValue() {
        // Honored even against the shape: a card-shaped folder saying `lane` is a lane.
        #expect(IntegrityRules.trashKind(kindValue: "lane", hasIdentityShapedChildIndex: false) == .lane)
        // And a lane-shaped folder saying `card` is a card.
        #expect(IntegrityRules.trashKind(kindValue: "card", hasIdentityShapedChildIndex: true) == .card)
    }

    /// An unrecognized value, or none, falls through to **shape**.
    @Test("An unrecognized value falls through to shape")
    func unrecognizedValueFallsToShape() {
        #expect(IntegrityRules.trashKind(kindValue: nil, hasIdentityShapedChildIndex: true) == .lane)
        #expect(IntegrityRules.trashKind(kindValue: nil, hasIdentityShapedChildIndex: false) == .card)
        #expect(IntegrityRules.trashKind(kindValue: "widget", hasIdentityShapedChildIndex: true) == .lane)
        #expect(IntegrityRules.trashKind(kindValue: "", hasIdentityShapedChildIndex: false) == .card)
        // `board` is not a third answer in the trash — a board cannot be trashed, so shape decides.
        #expect(IntegrityRules.trashKind(kindValue: "board", hasIdentityShapedChildIndex: true) == .lane)
    }
}

// MARK: - Per-kind validation

@Suite("IntegrityRules ▸ validation")
struct IntegrityValidationTests {

    private func bytes(_ text: String) -> Data { Data(text.utf8) }

    @Test("A board index validates without an order")
    func boardValidatesWithoutOrder() throws {
        let document = try IntegrityRules.validateIndex(
            bytes("---\nschema: 1\ntitle: Board\n---\nbody\n"),
            path: "index.md",
            kind: .board,
            supportedSchema: 1
        )
        #expect(document.title == .valid("Board"))
    }

    /// **`order` is optional at every kind** (01-storage-format.md § Ordering, re-ruled 2026-07-31):
    /// a lane or card without one reads as append-at-end rather than being refused, so the validator
    /// has nothing to say about it.
    @Test("A lane or card index without an order validates")
    func laneAndCardDoNotRequireOrder() throws {
        for kind in [IntegrityRules.ObjectKind.lane, .card] {
            let document = try IntegrityRules.validateIndex(
                bytes("---\nschema: 1\n---\nbody\n"),
                path: "index.md",
                kind: kind,
                supportedSchema: 1
            )
            #expect(document.order.isMissing)
        }
    }

    /// **The root's `schema` is required; below it, absence reads as 1** — the one per-kind
    /// difference the validator still draws.
    @Test("Schema is required at the board and optional below it")
    func schemaIsRequiredAtTheRootOnly() throws {
        let schemaless = bytes("---\ntitle: No Schema\n---\nbody\n")
        #expect(throws: BoardLoadError(path: "index.md", reason: .missingSchema)) {
            try IntegrityRules.validateIndex(
                schemaless, path: "index.md", kind: .board, supportedSchema: 1)
        }
        for kind in [IntegrityRules.ObjectKind.lane, .card, .comment] {
            let document = try IntegrityRules.validateIndex(
                schemaless, path: "index.md", kind: kind, supportedSchema: 1)
            #expect(document.schema.isMissing)
        }
    }

    /// The card window's gate is this rule at `kind: .card` — one function, not a copy.
    @Test("validateCardIndex is validateIndex at card")
    func cardValidatorIsTheGeneralOne() throws {
        // No `order`, no `schema` — the minimum agent card, and a legal raw-source Apply since
        // 2026-07-31.
        let minimum = try BoardLoader.validateCardIndex(bytes("---\ntitle: Minimum\n---\nbody\n"), path: "index.md")
        #expect(minimum.title == .valid("Minimum"))

        let newer = bytes("---\nschema: 99\norder: 1\n---\n")
        #expect(throws: BoardLoadError(path: "index.md", reason: .schemaNewerThanApp(found: 99))) {
            try BoardLoader.validateCardIndex(newer, path: "index.md")
        }
    }

    /// The rulebook's own readings, without a filesystem in the way.
    @Test("The order reading is stated over usability")
    func orderReadingIsStatedOverUsability() throws {
        func reading(_ frontmatter: String) throws -> (order: Double?, coerced: CoercedField?) {
            IntegrityRules.resolvedOrder(in: try FrontmatterDocument.parse("---\n\(frontmatter)---\n"))
        }
        #expect(try reading("order: 1024\n").order == 1024)
        #expect(try reading("order: 1024\n").coerced == nil)

        for (frontmatter, raw) in [
            ("schema: 1\n", ""), ("order:\n", ""), ("order: null\n", "null"),
            ("order: banana\n", "banana"), ("order: .nan\n", ".nan"), ("order: .inf\n", ".inf"),
        ] {
            let read = try reading(frontmatter)
            #expect(read.order == nil, "\(frontmatter) should be unusable")
            #expect(read.coerced == CoercedField(key: "order", raw: raw), "\(frontmatter)")
        }
    }

    /// The refuse-writes verdict's rule, named in the vocabulary rather than left as a property one
    /// call site happens to read.
    @Test("The uneditable shape is the document's, named here")
    func uneditableShapeIsNamed() throws {
        let flow = try FrontmatterDocument.parse("---\n{schema: 1, order: 1024}\n---\nbody\n")
        #expect(IntegrityRules.uneditableShape(of: flow) == .keyWithoutOwnLine)
        let ordinary = try FrontmatterDocument.parse("---\nschema: 1\norder: 1024\n---\nbody\n")
        #expect(IntegrityRules.uneditableShape(of: ordinary) == nil)
    }
}

// MARK: - On-touch heals

@Suite("IntegrityRules ▸ on-touch heals")
struct IntegrityOnTouchTests {

    @Test("A missing kind backfills with the object's own kind")
    func missingKindBackfills() throws {
        var document = try FrontmatterDocument.parse("---\nschema: 1\norder: 1024\n---\nbody\n")
        let heals = IntegrityRules.healOnTouch(&document, kind: .card)
        #expect(heals == [.kindBackfilled(.card)])
        #expect(document.kind == .valid("card"))
        // Appended before the closing delimiter, last — where the common table puts it.
        #expect(document.keys == ["schema", "order", "kind"])
        #expect(document.body == "body\n")
    }

    /// **The value is never rewritten, never corroborated, never stripped** — consumers trust it.
    @Test("A present kind is left exactly as written")
    func presentKindIsLeftAlone() throws {
        var document = try FrontmatterDocument.parse("---\nschema: 1\norder: 1\nkind: widget\n---\n")
        #expect(IntegrityRules.healOnTouch(&document, kind: .card).isEmpty)
        #expect(document.kind == .valid("widget"))
        // Even a value that contradicts position: the trash reader honors it, so the writer must not
        // "correct" it out from under whoever wrote it.
        var lane = try FrontmatterDocument.parse("---\nschema: 1\norder: 1\nkind: lane\n---\n")
        #expect(IntegrityRules.healOnTouch(&lane, kind: .card).isEmpty)
        #expect(lane.kind == .valid("lane"))
    }

    /// Null-as-missing, the engine's own rule, applied here: a key started and never given a value
    /// is absent, and so backfills.
    @Test("An explicit null backfills")
    func explicitNullBackfills() throws {
        var document = try FrontmatterDocument.parse("---\nschema: 1\norder: 1\nkind:\n---\n")
        #expect(IntegrityRules.healOnTouch(&document, kind: .lane) == [.kindBackfilled(.lane)])
        #expect(document.kind == .valid("lane"))
    }

    /// **A guessed kind is worse than an absent one**: where position cannot answer, nothing is
    /// stamped.
    @Test("No kind, no stamp")
    func unknownKindStampsNothing() throws {
        var document = try FrontmatterDocument.parse("---\nschema: 1\norder: 1\n---\n")
        #expect(IntegrityRules.healOnTouch(&document, kind: nil).isEmpty)
        #expect(document.kind == .missing)
    }

    /// **The rank materializes on touch** (01-storage-format.md § Ordering, re-ruled 2026-07-31):
    /// the value written is the append-at-end reading the board was already rendering, so nothing
    /// moves when the stamp lands.
    @Test("A missing order is stamped with the rank it read as")
    func missingOrderIsStamped() throws {
        var document = try FrontmatterDocument.parse("---\nschema: 1\ntitle: Minimum\n---\nbody\n")
        let heals = IntegrityRules.healOnTouch(&document, kind: .card, rank: 3072)
        #expect(heals == [.kindBackfilled(.card), .rankStamped(3072)])
        #expect(document.order == .valid(3072))
        #expect(document.keys == ["schema", "title", "kind", "order"])
        #expect(document.body == "body\n")
    }

    /// **Unlike `kind`, an unusable *present* value is replaced**: a rank has to be a number for the
    /// midpoint math to mean anything, so `banana` and `.nan` heal exactly like an absent key.
    @Test("An unusable order is stamped too")
    func unusableOrderIsStamped() throws {
        for text in ["order: banana", "order: .nan", "order:"] {
            var document = try FrontmatterDocument.parse("---\nschema: 1\n\(text)\nkind: card\n---\n")
            #expect(IntegrityRules.healOnTouch(&document, kind: .card, rank: 2048) == [.rankStamped(2048)])
            #expect(document.order == .valid(2048), "\(text)")
        }
    }

    /// A usable rank is never rewritten, and a caller with no rank to offer stamps nothing — the
    /// board root and a comment, which have no ladder to sit in.
    @Test("A present rank, and a nil rank, stamp nothing")
    func presentOrAbsentRankStampsNothing() throws {
        var ranked = try FrontmatterDocument.parse("---\nschema: 1\norder: 1024\nkind: card\n---\n")
        #expect(IntegrityRules.healOnTouch(&ranked, kind: .card, rank: 9999).isEmpty)
        #expect(ranked.order == .valid(1024))

        var rankless = try FrontmatterDocument.parse("---\nschema: 1\nkind: board\n---\n")
        #expect(IntegrityRules.healOnTouch(&rankless, kind: .board, rank: nil).isEmpty)
        #expect(rankless.order == .missing)
    }
}

// MARK: - The defect vocabulary

@Suite("IntegrityRules ▸ defects")
struct IntegrityDefectTests {

    private let lane = ItemID(rawValue: "11111111-1111-4111-8111-111111111111")
    private let card = ItemID(rawValue: "55555555-5555-4555-8555-555555555555")

    /// Each defect knows its heal class — the memo key and the banner-posture row.
    @Test("Every defect names its class")
    func defectsNameTheirClass() {
        #expect(IntegrityRules.Defect.looseCardFiles(
            LooseCardFiles(laneID: lane, cardID: card, title: nil, fileNames: ["a.txt"])
        ).healClass == .looseCardFiles)
        #expect(IntegrityRules.Defect.legacyTombstone(
            LegacyTombstone(laneID: lane, cardID: card, title: nil)
        ).healClass == .legacyTombstone)
        #expect(IntegrityRules.Defect.claimedNameSquatted(
            ClaimedNameSquatter(name: ".trash", found: .file, expected: .directory)
        ).healClass == .claimedNameSquatted)
    }

    /// **One signature per file for the loose-file defect**, so a card that gains or loses a single
    /// loose file is a different picture and earns a fresh attempt.
    @Test("A loose-file defect signs per file")
    func looseFilesSignPerFile() {
        let defect = IntegrityRules.Defect.looseCardFiles(
            LooseCardFiles(laneID: lane, cardID: card, title: "T", fileNames: ["a.txt", "b.txt"])
        )
        #expect(defect.signatures.count == 2)
        #expect(Set(defect.signatures) == [
            "loose:\(lane.rawValue)/\(card.rawValue)/a.txt",
            "loose:\(lane.rawValue)/\(card.rawValue)/b.txt",
        ])
    }

    /// The title is *not* in the signature: renaming a card does not make its pending relocation a
    /// new defect to retry.
    @Test("Signatures identify the work, not its display")
    func signaturesIdentifyTheWork() {
        let one = IntegrityRules.Defect.legacyTombstone(
            LegacyTombstone(laneID: lane, cardID: card, title: "Before")
        )
        let two = IntegrityRules.Defect.legacyTombstone(
            LegacyTombstone(laneID: lane, cardID: card, title: "After")
        )
        #expect(one.signatures == two.signatures)
    }

    /// The classes are disjoint by construction — a memo for one heal can never collide with
    /// another's picture.
    @Test("Signatures are namespaced per class")
    func signaturesAreNamespaced() {
        let loose = IntegrityRules.Defect.looseCardFiles(
            LooseCardFiles(laneID: lane, cardID: card, title: nil, fileNames: ["x"])
        )
        let tombstone = IntegrityRules.Defect.legacyTombstone(
            LegacyTombstone(laneID: lane, cardID: card, title: nil)
        )
        let squatter = IntegrityRules.Defect.claimedNameSquatted(
            ClaimedNameSquatter(name: ".trash", found: .symlink, expected: .directory)
        )
        let all = Set(loose.signatures + tombstone.signatures + squatter.signatures)
        #expect(all.count == 3)
        #expect(squatter.signatures == ["claimed:.trash:symlink"])
    }
}

// MARK: - The `kind` key, through the Writer

/// **`kind` is written at creation of every object and backfills on touch** (01-storage-format.md
/// § Frontmatter, re-ruled 2026-07-29) — the two halves of the rule, at the seam that implements
/// them (`BoardWriter.createBoard`/`createLane`/`createCard`, and `BoardWriter.updateIndex`).
///
/// **On-touch only, never a scheduled sweep**: nothing in the app walks a board adding this key, and
/// these tests are written so that a future sweep would fail them (an untouched sibling keeps no
/// `kind` at all).
@Suite("The kind key ▸ stamping and backfill")
struct ObjectKindWriteTests {

    private func kind(of relativePath: String, in fixture: WriterFixture) throws -> FieldValue<String> {
        try FrontmatterDocument.parse(fixture.indexText(relativePath)).kind
    }

    @Test("Every create path stamps its own kind")
    func createPathsStamp() throws {
        let fixture = try WriterFixture()
        defer { fixture.tearDown() }
        let root = fixture.url("MyBoard.kanban")

        try BoardWriter.createBoard(at: root, title: "Board")
        let lane = try BoardWriter.createLane(inBoard: root, title: "Todo")
        let card = try BoardWriter.createCard(
            inLane: root.appendingPathComponent(lane.rawValue, isDirectory: true),
            title: "Fix login"
        )

        #expect(try kind(of: "MyBoard.kanban", in: fixture) == .valid("board"))
        #expect(try kind(of: "MyBoard.kanban/\(lane.rawValue)", in: fixture) == .valid("lane"))
        #expect(try kind(of: "MyBoard.kanban/\(lane.rawValue)/\(card.rawValue)", in: fixture) == .valid("card"))
    }

    /// The backfill reads **position** — a lane's child is a card, a board root's child is a lane —
    /// so an older file gains the *right* value without the caller being asked for one.
    @Test("A rewrite backfills a missing kind from position")
    func rewriteBackfillsFromPosition() throws {
        let fixture = try WriterFixture()
        defer { fixture.tearDown() }
        try fixture.item("", Item.board)
        try fixture.item(Ident.lane1, Item.rich(order: "1024", title: "Todo"))
        try fixture.item("\(Ident.lane1)/\(Ident.card1)", Item.rich(order: "1024", title: "A"))
        try fixture.item("\(Ident.lane1)/\(Ident.card2)", Item.rich(order: "2048", title: "B"))

        try BoardWriter.updateIndex(
            inItemFolder: fixture.url("\(Ident.lane1)/\(Ident.card1)"),
            operation: .style(title: nil)
        ) { $0.setStyleValue("fern", for: FrontmatterKeys.background) }
        try BoardWriter.updateIndex(
            inItemFolder: fixture.url(Ident.lane1),
            operation: .style(title: nil)
        ) { $0.setStyleValue("fern", for: FrontmatterKeys.background) }

        #expect(try kind(of: "\(Ident.lane1)/\(Ident.card1)", in: fixture) == .valid("card"))
        #expect(try kind(of: Ident.lane1, in: fixture) == .valid("lane"))
        // **On touch only**: the sibling nobody wrote to still has no `kind`, which is exactly what
        // "never a scheduled backfill sweep" means on disk.
        #expect(try kind(of: "\(Ident.lane1)/\(Ident.card2)", in: fixture) == .missing)
        #expect(try kind(of: "", in: fixture) == .missing)
    }

    /// The board root is the one file whose kind position cannot answer, so its writers **declare**
    /// it — and the declaration is what the backfill uses.
    @Test("The board root's kind is declared by its writers")
    func boardRootKindIsDeclared() throws {
        let fixture = try WriterFixture()
        defer { fixture.tearDown() }
        try fixture.item("", Item.board)

        try BoardWriter.updateIndex(inItemFolder: fixture.root, kind: .board, operation: .rename(title: nil)) {
            $0.set(FrontmatterKeys.title, to: .string("Renamed"))
        }

        #expect(try kind(of: "", in: fixture) == .valid("board"))
    }

    /// Undeclared and unanswerable by position → **nothing is stamped**. A guessed kind on disk
    /// would be worse than an absent one, because the trash's discriminator trusts what it finds.
    @Test("A hand-named folder is stamped with nothing")
    func unknownPositionStampsNothing() throws {
        let fixture = try WriterFixture()
        defer { fixture.tearDown() }
        let folder = try fixture.item("notes", Item.rich(order: "1024", title: "Hand-made"))

        try BoardWriter.updateIndex(inItemFolder: folder, operation: .style(title: nil)) {
            $0.setStyleValue("fern", for: FrontmatterKeys.background)
        }

        #expect(try kind(of: "notes", in: fixture) == .missing)
    }

    /// Inside `.trash/` position cannot answer either, so **shape** does — the same rule the trash
    /// reader uses, so a backfilled value and a read value can never disagree.
    @Test("A trashed folder backfills by shape")
    func trashBackfillsByShape() throws {
        let fixture = try WriterFixture()
        defer { fixture.tearDown() }
        try fixture.item("", Item.board)
        try fixture.item(".trash/\(Ident.card1)", Item.rich(order: "1024", title: "A trashed card"))
        try fixture.item(".trash/\(Ident.lane2)", Item.rich(order: "2048", title: "A trashed lane"))
        try fixture.item(".trash/\(Ident.lane2)/\(Ident.card3)", Item.rich(order: "1024", title: "Its card"))

        for path in [".trash/\(Ident.card1)", ".trash/\(Ident.lane2)"] {
            try BoardWriter.updateIndex(inItemFolder: fixture.url(path), operation: .reorder(title: nil)) {
                $0.set(FrontmatterKeys.order, to: .double(4096))
            }
        }

        #expect(try kind(of: ".trash/\(Ident.card1)", in: fixture) == .valid("card"))
        #expect(try kind(of: ".trash/\(Ident.lane2)", in: fixture) == .valid("lane"))
    }

    /// **The trash move's own rank mint is a touch** — 01's own example of where the key earns its
    /// keep ("any Writer rewrite of that lane's `index.md`, the trash move's rank mint included").
    @Test("Deleting a card backfills its kind on the way into the trash")
    func deleteBackfillsOnTheWayIn() throws {
        let fixture = try WriterFixture()
        defer { fixture.tearDown() }
        try fixture.item("", Item.board)
        try fixture.item(Ident.lane1, Item.rich(order: "1024", title: "Todo"))
        try fixture.item("\(Ident.lane1)/\(Ident.card1)", Item.rich(order: "1024", title: "A"))

        try BoardWriter.deleteCardToTrash(
            at: fixture.url("\(Ident.lane1)/\(Ident.card1)"),
            inBoard: fixture.root
        )

        #expect(try kind(of: ".trash/\(Ident.card1)", in: fixture) == .valid("card"))
    }

    /// Templates gain the key **lazily**, through this same backfill — there is no template
    /// migration, by design.
    @Test("A copy carries what the source had, and heals what it did not")
    func copiesInheritAndHeal() throws {
        let fixture = try WriterFixture()
        defer { fixture.tearDown() }
        try fixture.item("", Item.board)
        try fixture.item(Ident.lane1, Item.rich(order: "1024", title: "Todo"))
        try fixture.item("\(Ident.lane1)/\(Ident.card1)", Item.rich(order: "1024", title: "A"))

        let copy = try BoardWriter.copyItem(
            at: fixture.url("\(Ident.lane1)/\(Ident.card1)"),
            toParent: fixture.url(Ident.lane1),
            order: 2048,
            stamps: .fork
        )

        // The copy's own `index.md` is rewritten by the copy (stamps, order), so it heals in flight.
        #expect(try kind(of: "\(Ident.lane1)/\(copy.rawValue)", in: fixture) == .valid("card"))
    }
}
