Realign code with the 2026-07-29 findings-resolution rulings

Nine rulings land as code. Reorders don't stamp — one container-change
predicate (WriteOperation.rewritesOrderOnly): within-container reorders
and the renumber rescale rewrite only order, while cross-lane, cross-board,
and trash moves stamp modified and clear modified-by; no trash special
case exists, and the m8 undo inverses conform through the same seam.
Copies are transactions: the root-strict/nested-lenient split retires for
a whole-subtree stampability preflight that refuses loudly naming the
offender, and every item-level copy severs remote/remote-state at every
level (whole-board forks carry them verbatim). Paste refuses, never
degrades: the embedded-index.md materialization and its loss row retire;
a missing staged snapshot produces nothing and posts an error-tone
one-shot named from manifest metadata. Coerce-tier fallbacks log through
the Defect stream with path context attached loader-side. Displacement is
level-uniform: a file squatting attachments inside a card heals by the
same rename ladder as board-root squatters; comments stays tolerated.
Delete Immediately joins card and lane context menus as Delete's
⌥-alternate with its own VO custom action, routed through an explicit
container so the menu target outranks standing selection. Agent guide v7
teaches the stamp discipline and the card-level attachments claim, and
sheds two stale v6 lines (lanes trash now; kind is taught). Verified
conformant, unchanged: edition-aware Undo/Redo disable, trash marquee
full-height backdrop.

Both schemes 1854 tests / 318 suites green; verify-editions 30/30.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
2026-07-30 06:49:11 -04:00
parent 5ae48de0ea
commit 69084fdff7
27 changed files with 2159 additions and 542 deletions
+24
View File
@@ -647,6 +647,25 @@ struct AgentGuideContentTests {
#expect(content.contains("schema: 1"))
}
/// v7's list, 08 The agent guide's "v7 additionally teaches" bullet: the refined
/// stamp-discipline predicate (01-storage-format.md `modified`'s scope, ruled 2026-07-29,
/// refined 2026-07-30) and the card-level `attachments` claimed name (01-storage-format.md
/// § Fractal layout Rules, "level-uniform"). Each pin is a phrase an agent reading the guide
/// would actually see, not a paraphrase so a wording rewrite that silently drops the rule
/// fails here instead of shipping quietly.
@Test("v7 teaches the stamp-discipline predicate and the attachments claimed name")
func v7VocabularyIsPresent() {
let content = AgentGuide.content
// The predicate itself: one rule, not a trash special case.
#expect(content.contains("a move that changes an item's container"))
#expect(content.contains("rewrites only `order`"))
#expect(content.contains("into/out of `.trash/`"))
#expect(content.contains("The trash move isn't an exception"))
// The attachments claimed name: don't squat the folder's name with a file.
#expect(content.contains("The name `attachments` itself belongs to that folder"))
#expect(content.contains("*file* called `attachments` in a card"))
}
/// The pathfinder's guide taught `media/` and tombstone deletes; both are retired
/// (01-storage-format.md Changes from the pathfinder schema; Deletion). The one legitimate
/// mention of `deleted:` is the warning never to write it.
@@ -656,5 +675,10 @@ struct AgentGuideContentTests {
#expect(!content.contains("media/"))
#expect(!content.contains("tombstone"))
#expect(content.contains("Never write a `deleted:` key"))
// The 2026-07-29 rule was first named "moves-don't-stamp"; the 2026-07-30 refinement
// retired that framing (container changes stamp, the trash move included) the guide
// must never teach the superseded shape of the rule.
#expect(!content.contains("moves don't stamp"))
#expect(!content.contains("moves-don't-stamp"))
}
}
+127
View File
@@ -828,3 +828,130 @@ struct BoardLoaderEncodingTests {
}
}
}
// MARK: - The coerce tier's trace (01-storage-format.md § Frontmatter, ruled 2026-07-29)
/// **"A no-sensible-reading fallback logs"** field, path, and raw text, carried as coerce-tier
/// entries in the integrity service's Defect stream:
///
/// > the one place where an observed-in-the-wild shape can later be promoted to a heuristic heal or a
/// > notice; no banner, no behavior change.
///
/// So these tests assert two things at once, and the second matters as much as the first: the fallback
/// is *reported*, and nothing about the board changed because of it the field still renders its
/// default, the bytes on disk are still verbatim, and no heal is scheduled.
@Suite("BoardLoader ▸ coerce-tier fallbacks")
struct BoardLoaderCoercionTraceTests {
/// The pure half first: which lenient fields report, and which deliberately do not.
///
/// `schema` and `order` are the **refuse** tier a malformed one fails the load loudly, so there is
/// no silent recovery to leave a trace of and `deleted`'s rule is presence-not-validity, so
/// nothing falls back to a default there either.
@Test("The document reports its lenient fallbacks, and only those")
func theDocumentReportsItsLenientFallbacks() throws {
let document = try FrontmatterDocument.parse("""
---
schema: 1
order: 1024
title: [a, b]
width: 1.5
created: not-a-date
icon: {a: b}
deleted: also-not-a-date
---
Body.
""")
let byKey = Dictionary(uniqueKeysWithValues: document.coercedFields.map { ($0.key, $0.raw) })
#expect(Set(byKey.keys) == ["title", "width", "created", "icon"])
#expect(byKey["width"] == "1.5", "the raw text as written — what a future heuristic would read")
#expect(byKey["created"] == "not-a-date")
#expect(byKey["deleted"] == nil, "presence, not validity, decides a tombstone")
}
@Test("A clean document reports nothing")
func aCleanDocumentReportsNothing() throws {
let document = try FrontmatterDocument.parse("""
---
schema: 1
order: 1024
title: Fine
width: 2
---
""")
#expect(document.coercedFields.isEmpty)
}
/// **A scalar of the wrong type is not a fallback** it coerces to the text the author typed
/// (`title: 2048` reads as "2048"), which is a *successful* reading and leaves no trace. Only "no
/// sensible reading exists" does.
@Test("A coerced scalar leaves no trace — it was read, not defaulted")
func aCoercedScalarLeavesNoTrace() throws {
let document = try FrontmatterDocument.parse("---\nschema: 1\ntitle: 2048\nwidth: \"3\"\n---\n")
#expect(document.title.value == "2048")
#expect(document.width.value == 3)
#expect(document.coercedFields.isEmpty)
}
/// The loader's half: the path is attached at every level, because the rule is about fields and
/// every level has them.
@Test("The loader attaches the path, at every level")
func theLoaderAttachesThePath() throws {
let fixture = try BoardFixture()
defer { fixture.tearDown() }
let lane = uuidFolderName()
let card = uuidFolderName()
let trashed = uuidFolderName()
try fixture.index("", "schema: 1\ntitle: [a, b]\n")
try fixture.index(lane, "schema: 1\norder: 1024\nwidth: 1.5\n")
try fixture.index("\(lane)/\(card)", "schema: 1\norder: 1024\nicon: {x: y}\n")
try fixture.index(".trash/\(trashed)", "schema: 1\norder: 1024\ncreated: nope\n")
let reported = try BoardLoader.load(boardRoot: fixture.root).coercedFrontmatter
let byPath = Dictionary(uniqueKeysWithValues: reported.map { ($0.path, $0.fields.map(\.key)) })
#expect(byPath["index.md"] == ["title"])
#expect(byPath["\(lane)/index.md"] == ["width"])
#expect(byPath["\(lane)/\(card)/index.md"] == ["icon"])
#expect(byPath[".trash/\(trashed)/index.md"] == ["created"])
}
/// **No behavior change** the whole point of the tier. The fields render their defaults exactly as
/// they did before anything was reported, and the bytes are preserved verbatim.
@Test("Nothing about the board changes — defaults render, bytes stay")
func nothingChanges() throws {
let fixture = try BoardFixture()
defer { fixture.tearDown() }
let lane = uuidFolderName()
try fixture.index("", "schema: 1\ntitle: Board\n")
try fixture.index(lane, "schema: 1\norder: 1024\ntitle: [a, b]\nwidth: 0.5\n")
let before = try Data(contentsOf: fixture.root.appendingPathComponent("\(lane)/index.md"))
let result = try BoardLoader.load(boardRoot: fixture.root)
let loaded = try #require(result.model.lanes.first)
#expect(loaded.title.isMalformed, "the field still reads as malformed")
#expect(loaded.title.value == nil, "and renders its default — the untitled placeholder")
#expect(loaded.width.value == nil, "width falls back to 1 at the render layer, not here")
#expect(try Data(contentsOf: fixture.root.appendingPathComponent("\(lane)/index.md")) == before,
"read-side only: the loader never writes")
#expect(result.warnings.isEmpty, "a coercion is not a stray warning")
}
/// **It is not healable work**, which is why it has no class: a class is a memo key and a
/// banner-posture row in the engine, and inventing one would arm a memo against a repair nobody
/// wrote. The other defects keep theirs.
@Test("A coerce-tier defect has no heal class, and signs per field")
func itHasNoHealClass() {
let defect = IntegrityRules.Defect.coercedFrontmatter(CoercedFrontmatter(
path: "lane/card/index.md",
fields: [CoercedField(key: "width", raw: "1.5"), CoercedField(key: "icon", raw: "{}")]
))
#expect(defect.healClass == nil)
#expect(Set(defect.signatures) == [
"coerce:lane/card/index.md:width",
"coerce:lane/card/index.md:icon",
])
}
}
+143 -17
View File
@@ -423,18 +423,28 @@ struct BoardWriterRenumberTests {
#expect(try fixture.indexText("lane/\(Child.b)").contains("order: 1024\n"))
}
@Test func eachRewrittenChildIsStampedAndKeepsItsUnknownKeys() throws {
/// **A rescale stamps nothing** (01-storage-format.md § Ordering, verbatim: "order-only rewrites,
/// so no `modified` stamp and no `modified-by` clear"; § Frontmatter `modified`'s scope, refined
/// 2026-07-30). Every sibling's file is rewritten and not one of them is stamped a foreign
/// `modified-by` survives, which is the pairing read at its sharpest: attribution cannot change when
/// content didn't.
///
/// The prior version of this test asserted the opposite (`modifiedBy == .missing`, `modified != nil`)
/// under the pre-2026-07-29 rule that every app write stamps.
@Test func eachRewrittenChildKeepsItsStampsAndItsUnknownKeys() throws {
let fixture = try WriterFixture()
defer { fixture.tearDown() }
let lane = try crowdedLane(fixture)
let priorModified = try FrontmatterDocument.parse(fixture.indexText("lane/\(Child.a)"))
.rawValue(for: FrontmatterKeys.modified)
try BoardWriter.renumberVisibleChildren(of: lane)
for name in [Child.a, Child.b, Child.c] {
let text = try fixture.indexText("lane/\(name)")
let document = try FrontmatterDocument.parse(text)
#expect(document.modifiedBy == .missing)
#expect(document.modified.value != nil)
#expect(document.modifiedBy == .valid("claude"), "a rescale touches no content, so attribution stands")
#expect(document.rawValue(for: FrontmatterKeys.modified) == priorModified, "and nothing is stamped")
#expect(document.unknownFields.map(\.key) == ["project"])
#expect(text.contains("project: lanework # agent overlay\n"))
#expect(document.body.hasSuffix(" body\n"))
@@ -1458,14 +1468,81 @@ struct BoardWriterCopyTests {
}
}
/// The leniency below the root: a nested file the surgical editor cannot key is copied
/// verbatim rather than failing the gesture stale `modified-by` and all while its
/// editable siblings are stamped normally.
@Test func aNestedUneditableFileCopiesVerbatimWhileItsSiblingsAreStamped() throws {
/// **A copy is a transaction** (01-storage-format.md § Frontmatter, ruled 2026-07-29): a nested
/// card the surgical editor cannot key refuses the **whole** copy, naming that card, and nothing is
/// materialized at the destination.
///
/// This replaced the former nested leniency, which copied such a card verbatim stale `modified-by`
/// and all and stamped its siblings normally. The kindness was the one verdict 01's doctrine
/// forbids: "proceed partially, lose a little" is never a verdict, and a silently unstamped
/// descendant now also carries a live tracker claim it has no right to (the `remote` sever).
@Test func aNestedUneditableFileRefusesTheWholeCopyNamingIt() throws {
let fixture = try WriterFixture()
defer { fixture.tearDown() }
try board(fixture)
// `Item.uneditable`'s own title, so the refusal has a name to carry.
try fixture.item("A.kanban/\(Ident.lane1)/\(Ident.card3)", Item.uneditable)
let before = try fixture.entryNames("A.kanban")
let error = writeFailure {
_ = try BoardWriter.copyItem(
at: fixture.url("A.kanban/\(Ident.lane1)"),
toParent: fixture.url("A.kanban"),
order: nil,
stamps: .fork
)
}
let failure = try #require(error)
#expect(failure.operation == .copy(title: "Odd"), "the refusal names the offending item")
if case .uneditableFrontmatter = failure.reason {} else {
Issue.record("expected the uneditable-shape refusal, got \(failure.reason)")
}
#expect(failure.path.contains(Ident.card3), "and the offending file's own path")
#expect(try fixture.entryNames("A.kanban") == before, "nothing was materialized")
}
/// The preflight runs over the **source**, so a refusal is free: the copy is refused before a single
/// byte is written, rather than materialized and then cleaned up.
@Test func aRefusedCopyNeverTouchesTheDestinationAtAll() throws {
let fixture = try WriterFixture()
defer { fixture.tearDown() }
try board(fixture)
try fixture.item("A.kanban/\(Ident.lane1)/\(Ident.card3)", Item.uneditable)
// A second board, so "nothing at the destination" is a claim about an empty container rather
// than about a folder that happens to hold the source too.
try fixture.item("B.kanban", Item.board)
try fixture.item("B.kanban/\(Ident.lane3)", Item.rich(order: "1024", title: "Elsewhere"))
_ = writeFailure {
_ = try BoardWriter.copyItem(
at: fixture.url("A.kanban/\(Ident.lane1)"),
toParent: fixture.url("B.kanban"),
order: nil,
stamps: .fork
)
}
#expect(try fixture.entryNames("B.kanban").sorted() == ["index.md", Ident.lane3].sorted())
}
/// **Item-level copies sever tracker identity** (01-storage-format.md § Fractal layout Rules,
/// ruled 2026-07-29): "every folder an item-level copy materializes drops the reserved
/// `remote`/`remote-state` keys, at every level because two local objects must never both claim to
/// be the same remote object". The **source** keeps both, because a sever is something a copy does to
/// itself.
///
/// A lane copy, so both keys are exercised where the schema puts them `remote-state` on the lane,
/// `remote` on its cards and both levels are asserted, which is what "at every level" means.
@Test func anItemLevelCopySeversTheReservedTrackerKeys() throws {
let fixture = try WriterFixture()
defer { fixture.tearDown() }
try fixture.item("A.kanban", Item.board)
try fixture.item("A.kanban/\(Ident.lane1)", Item.tracked(order: "1024", title: "Todo", key: "remote-state"))
try fixture.item(
"A.kanban/\(Ident.lane1)/\(Ident.card1)",
Item.tracked(order: "1024", title: "Card One", key: "remote")
)
let id = try BoardWriter.copyItem(
at: fixture.url("A.kanban/\(Ident.lane1)"),
@@ -1474,10 +1551,51 @@ struct BoardWriterCopyTests {
stamps: .fork
)
let copied = try childrenByTitle(of: "A.kanban/\(id.rawValue)", in: fixture)
#expect(try fixture.indexText("A.kanban/\(id.rawValue)/\(try #require(copied["Odd"]))") == Item.uneditable)
#expect(try FrontmatterDocument.parse(fixture.indexText("A.kanban/\(id.rawValue)/\(try #require(copied["Card One"]))"))
.modifiedBy == .missing)
let lane = try FrontmatterDocument.parse(fixture.indexText("A.kanban/\(id.rawValue)"))
#expect(lane.value(for: "remote-state") == nil, "the copied lane's tracker mapping is severed")
#expect(lane.value(for: "project") != nil, "and every other unknown key is untouched")
let copiedCard = try #require(try fixture.entryNames("A.kanban/\(id.rawValue)").first(where: BoardLoader.isUUIDShaped))
let card = try FrontmatterDocument.parse(fixture.indexText("A.kanban/\(id.rawValue)/\(copiedCard)"))
#expect(card.value(for: "remote") == nil, "the copied card's too — at every level")
#expect(card.value(for: "project") != nil)
// The originals still claim their remote objects: only the copy is severed.
#expect(try FrontmatterDocument.parse(fixture.indexText("A.kanban/\(Ident.lane1)"))
.value(for: "remote-state") != nil)
#expect(try FrontmatterDocument.parse(fixture.indexText("A.kanban/\(Ident.lane1)/\(Ident.card1)"))
.value(for: "remote") != nil)
}
/// Every occurrence goes, not just the winning one `FrontmatterDocument.remove`'s own rule, and it
/// matters here more than anywhere: a hand-duplicated `remote:` left behind would resurrect the
/// claim the moment the winner were removed.
@Test func theSeverTakesEveryOccurrenceOfTheKey() throws {
let fixture = try WriterFixture()
defer { fixture.tearDown() }
try fixture.item("A.kanban", Item.board)
try fixture.item("A.kanban/\(Ident.lane1)", Item.rich(order: "1024", title: "Todo"))
try fixture.item("A.kanban/\(Ident.lane1)/\(Ident.card1)", """
---
schema: 1
title: Twinned
order: 1024
remote: gitea#1
remote: gitea#2
---
Body.
""")
let id = try BoardWriter.copyItem(
at: fixture.url("A.kanban/\(Ident.lane1)/\(Ident.card1)"),
toParent: fixture.url("A.kanban/\(Ident.lane1)"),
order: nil,
stamps: .fork
)
let text = try fixture.indexText("A.kanban/\(Ident.lane1)/\(id.rawValue)")
#expect(!text.contains("remote:"), "both occurrences went")
}
/// A UUID-shaped folder with no `index.md` interrupted-create residue is reminted and
@@ -1529,9 +1647,12 @@ struct BoardWriterCopyTests {
#expect(try fixture.entryNames("A.kanban/\(Ident.lane2)") == before)
}
/// All-or-nothing at the destination: a failure part-way through leaves no half-copied tree,
/// because a partial copy is pure residue nothing was there before.
@Test func aFailedCopyLeavesNothingAtTheDestination() throws {
/// All-or-nothing at the destination and since the copy became a **transaction** (ruled
/// 2026-07-29) this case never even materializes: an unreadable nested `index.md` is caught by the
/// preflight, over the *source*, before a byte is copied. The claim is the same one, met earlier and
/// more cheaply: nothing is at the destination, and `.unreadable` names the file rather than the
/// half-finished copy of it.
@Test func anUnreadableDescendantRefusesTheCopyBeforeItStarts() throws {
let fixture = try WriterFixture()
defer { fixture.tearDown() }
try board(fixture)
@@ -1548,10 +1669,11 @@ struct BoardWriterCopyTests {
stamps: .fork
)
}
guard case .io = error?.reason else {
Issue.record("expected .io, got \(String(describing: error?.reason))")
guard case .unreadable = error?.reason else {
Issue.record("expected .unreadable, got \(String(describing: error?.reason))")
return
}
#expect(error?.path.contains(Ident.card2) == true, "the offending file is named")
#expect(try fixture.entryNames("A.kanban") == before)
}
@@ -1668,7 +1790,11 @@ struct BoardWriterSameParentMoveTests {
// and not after itself miscounted (which would give 2048... or 4096+1024).
let text = try fixture.indexText("A.kanban/\(Ident.lane1)/\(Ident.card1)")
#expect(text.contains("order: 4096"))
#expect(!text.contains("modified-by:"))
// **The same-parent path is a reorder, so it stamps nothing** (01-storage-format.md
// § Frontmatter `modified`'s scope, refined 2026-07-30): the container never changed, so the
// foreign `modified-by` survives. This assertion was `!text.contains("modified-by:")` under the
// pre-refinement rule that every app write clears it.
#expect(text.contains("modified-by: claude"))
}
@Test func aSameParentMoveWithAnExplicitOrderJustRewritesIt() throws {
+171
View File
@@ -287,3 +287,174 @@ struct ClaimedNamePhrasingTests {
== "Couldn't move '.trash' aside — Lanework needs that name — permission denied")
}
}
// MARK: - The card level
/// **The rule is level-uniform** (01-storage-format.md § Fractal layout Rules, extended
/// 2026-07-29):
///
/// > a card's reserved child names are claimed the same way a regular file or symlink squatting
/// > `attachments` (a directory name) displaces by the same ladder (`attachments` `attachments 2`),
/// > so imports, Finder drops, and the sidebar listing never fail one gesture at a time against a
/// > squatted name; the displaced file, now an ordinary loose file, rides the next relocation into the
/// > real `attachments/` the heals compose.
///
/// The **reserved-but-unconsumed `comments`** is the timing principle's own illustration and is
/// deliberately *not* displaced: nothing reads that name until the tracker era, so a wrong-kind holder
/// degrades nothing while it stands and keeps the tolerated-stray posture.
@MainActor
@Suite("Claimed names ▸ the card level")
struct CardClaimedNameTests {
/// A card holding a *file* called `attachments`. The load reports it and moves nothing detection
/// is read-only at every level.
@Test("A file on a card's attachments is reported as a defect, and the load moves nothing")
func fileOnAttachmentsIsADefect() throws {
let fixture = try makeBoard()
defer { fixture.tearDown() }
try fixture.file("\(Ident.lane1)/\(Ident.card1)/attachments", Data("not a folder".utf8))
let result = try BoardLoader.load(boardRoot: fixture.root)
#expect(result.claimedNameSquatters == [
ClaimedNameSquatter(
name: "attachments",
found: .file,
expected: .directory,
location: .card(path: "\(Ident.lane1)/\(Ident.card1)")
),
])
#expect(
try fixture.data("\(Ident.lane1)/\(Ident.card1)/attachments") == Data("not a folder".utf8),
"the loader never writes"
)
// A claimed name is not a stray, so it never earns the stray-tolerance vocabulary and it is
// not a loose file either, so the relocation has nothing to say about it yet.
#expect(result.warnings.isEmpty)
#expect(result.looseCardFiles.isEmpty)
}
/// A **symlink** wearing the name is the same defect and is moved *as a link*, never followed
/// (01 § Fractal layout Rules: "symlinks are never traversed").
@Test("A symlink on a card's attachments is the same defect")
func symlinkOnAttachmentsIsADefect() throws {
let fixture = try makeBoard()
defer { fixture.tearDown() }
try FileManager.default.createSymbolicLink(
atPath: fixture.url("\(Ident.lane1)/\(Ident.card1)").appendingPathComponent("attachments").path,
withDestinationPath: "../elsewhere"
)
let result = try BoardLoader.load(boardRoot: fixture.root)
#expect(result.claimedNameSquatters.map(\.found) == [.symlink])
}
/// **`comments` stays tolerated** the timing principle, stated as the absence of a defect.
@Test("A file on a card's comments is not displaced — the name is not load-bearing yet")
func fileOnCommentsIsTolerated() throws {
let fixture = try makeBoard()
defer { fixture.tearDown() }
try fixture.file("\(Ident.lane1)/\(Ident.card1)/comments", Data("someday".utf8))
let result = try BoardLoader.load(boardRoot: fixture.root)
#expect(result.claimedNameSquatters.isEmpty)
#expect(result.looseCardFiles.isEmpty, "a reserved name is not a loose file either")
}
/// A real `attachments/` folder is a resident, not a squatter the check is about the node's
/// *kind*, and this is the negative case that keeps it honest.
@Test("A real attachments folder is no defect at all")
func aRealAttachmentsFolderIsFine() throws {
let fixture = try makeBoard()
defer { fixture.tearDown() }
try fixture.file("\(Ident.lane1)/\(Ident.card1)/attachments/shot.png", Data([0x01]))
#expect(try BoardLoader.load(boardRoot: fixture.root).claimedNameSquatters.isEmpty)
}
/// The heal itself, end to end: the ladder renames it inside the **card's** folder, the notice names
/// old and new, and the file's bytes are exactly what they were.
@Test("The heal displaces it by the ladder, inside the card's own folder")
func theHealDisplacesItInsideTheCard() async throws {
let fixture = try makeBoard()
defer { fixture.tearDown() }
let cardPath = "\(Ident.lane1)/\(Ident.card1)"
try fixture.file("\(cardPath)/attachments", Data("squatter".utf8))
let store = try BoardStore(rootURL: fixture.root)
store.displaceClaimedNames()
await store.awaitQuiescence()
#expect(try fixture.data("\(cardPath)/attachments 2") == Data("squatter".utf8), "preserved verbatim")
#expect(
IntegrityRules.node(at: fixture.url(cardPath).appendingPathComponent("attachments")) == nil,
"and the name is free for the app"
)
#expect(store.banners.losses.count == 1)
let message = try #require(store.banners.losses.first?.message)
#expect(message.contains("attachments"))
#expect(message.contains("attachments 2"))
}
/// **The heals compose** 01's own word for it: once displaced, the file is an ordinary loose file
/// beside the card's `index.md`, which is exactly what the loose-file relocation exists for. One
/// reload later it is inside the real `attachments/`.
@Test("Displaced, then relocated: the heals compose")
func theHealsCompose() async throws {
let fixture = try makeBoard()
defer { fixture.tearDown() }
let cardPath = "\(Ident.lane1)/\(Ident.card1)"
try fixture.file("\(cardPath)/attachments", Data("squatter".utf8))
let store = try BoardStore(rootURL: fixture.root)
store.displaceClaimedNames()
await store.awaitQuiescence()
// The next load sees an ordinary loose file where the squatter was.
let after = try BoardLoader.load(boardRoot: fixture.root)
#expect(after.claimedNameSquatters.isEmpty)
#expect(after.looseCardFiles.map(\.fileNames) == [["attachments 2"]])
let relocating = try BoardStore(rootURL: fixture.root)
relocating.relocateLooseCardFiles()
await relocating.awaitQuiescence()
#expect(
try fixture.data("\(cardPath)/attachments/attachments 2") == Data("squatter".utf8),
"and it landed in the real attachments/"
)
}
/// Two cards squatting the name are **two pieces of work** in one bracket the signature carries
/// the location, so one card's failed heal has no claim to have failed the other's.
@Test("Two squatted cards are two defects, healed in one bracket")
func twoCardsAreTwoDefects() async throws {
let fixture = try makeBoard()
defer { fixture.tearDown() }
try fixture.item("\(Ident.lane1)/\(Ident.card2)", Item.rich(order: "2048", title: "Second"))
try fixture.file("\(Ident.lane1)/\(Ident.card1)/attachments", Data("one".utf8))
try fixture.file("\(Ident.lane1)/\(Ident.card2)/attachments", Data("two".utf8))
let result = try BoardLoader.load(boardRoot: fixture.root)
#expect(result.claimedNameSquatters.count == 2)
#expect(Set(result.defects.flatMap(\.signatures)).count == 2, "distinct work, by location")
let store = try BoardStore(rootURL: fixture.root)
store.displaceClaimedNames()
await store.awaitQuiescence()
#expect(try fixture.data("\(Ident.lane1)/\(Ident.card1)/attachments 2") == Data("one".utf8))
#expect(try fixture.data("\(Ident.lane1)/\(Ident.card2)/attachments 2") == Data("two".utf8))
}
/// The table is the only thing to edit when `comments` graduates pinned so the split is a stated
/// rule rather than an accident of the probe's implementation.
@Test("The card-level table claims attachments and comments, and displaces only attachments")
func theTableStatesTheSplit() {
let names = IntegrityRules.claimedCardChildNames
#expect(names.map(\.name) == ["attachments", "comments"])
#expect(names.allSatisfy { $0.expected == .directory })
#expect(names.first { $0.name == "attachments" }?.displacesSquatters == true)
#expect(names.first { $0.name == "comments" }?.displacesSquatters == false)
}
}
+72 -59
View File
@@ -181,8 +181,8 @@ struct ClipboardManifestTests {
#expect(ClipboardManifest(data: data) == nil)
}
@Test("A lane entry's lost-attachment count totals its cards'")
func lostAttachments() {
@Test("A lane entry's attachment count totals its cards'")
func totalAttachments() {
let lane = ClipboardManifest.Entry(
id: Ident.lane1,
folder: Ident.lane1,
@@ -194,7 +194,7 @@ struct ClipboardManifestTests {
.init(id: Ident.card2, title: "Second", index: "b", attachmentCount: 1),
]
)
#expect(lane.lostAttachmentCount == 3)
#expect(lane.totalAttachmentCount == 3)
}
@Test("The plain-text rendering is the titles, untitled items rendered as the board renders them")
@@ -285,7 +285,7 @@ struct ClipboardCopyTests {
#expect(manifest.kind == .lane)
#expect(manifest.entries.map(\.id) == [Ident.lane1])
#expect(manifest.entries[0].cards.map(\.id) == [Ident.card1, Ident.card2])
#expect(manifest.entries[0].lostAttachmentCount == 2)
#expect(manifest.entries[0].totalAttachmentCount == 2)
}
@Test("A trash selection copies out, container recorded")
@@ -830,73 +830,86 @@ struct PasteTargetTests {
}
}
// MARK: - The degraded paste's phrasing
// MARK: - The refused paste's phrasing
@Suite("BannerCenter ▸ degraded paste")
struct DegradedPasteBannerTests {
/// **04-interactions.md Clipboard, re-ruled 2026-07-29** refuse, never degrade:
///
/// > A paste whose staged snapshot is missing or unreadable refuses loudly never degrades
/// > the paste produces **nothing**, and a one-shot failure banner names it from the manifest's
/// > metadata ("Couldn't paste 'Fix login' the copied content is gone").
///
/// The retired suite these replace pinned `degradedPasteMessage(for:)` and its loss row "Pasted
/// 'Fix login' without its 3 attachments". Both are gone with the degraded materialization: nothing
/// arrives, so there is no partial arrival to account for.
@Suite("BannerCenter ▸ refused paste")
struct RefusedPasteBannerTests {
/// 04's own example sentence, composed the way every failure headline is: the action clause the
/// banner owns, an em dash, the cause.
@Test("04's own example sentence")
@MainActor
func theExampleSentence() {
#expect(BannerCenter.degradedPasteMessage(
for: [.init(title: "Fix login", attachments: 3)]
) == "Pasted 'Fix login' without its 3 attachments")
let center = BannerCenter()
center.postRefusedPaste(title: "Fix login", stagedAt: "/tmp/staging/abc")
#expect(center.oneShots.count == 1)
let headline = try? #require(center.oneShots.first).error
#expect(headline.map(BannerCenter.headline(for:)) == "Couldn't paste 'Fix login' — the copied content is gone")
}
@Test("One attachment is singular")
func singular() {
#expect(BannerCenter.degradedPasteMessage(
for: [.init(title: "Fix login", attachments: 1)]
) == "Pasted 'Fix login' without its attachment")
}
@Test("An untitled item is 'the item', never the Untitled rendering")
/// "Untitled" is a rendering, never a value (03-board-ui.md § Card face), so an untitled entry is
/// "the item" `actionPhrase`'s standing convention for a failure with no title to quote.
@Test("An untitled entry is 'the item', never the Untitled rendering")
@MainActor
func untitled() {
#expect(BannerCenter.degradedPasteMessage(
for: [.init(title: nil, attachments: 2)]
) == "Pasted the item without its 2 attachments")
}
@Test("Several items total their attachments rather than listing titles")
func several() {
#expect(BannerCenter.degradedPasteMessage(
for: [.init(title: "A", attachments: 2), .init(title: "B", attachments: 3)]
) == "Pasted 2 items without their 5 attachments")
}
@Test("Nothing lost says nothing")
func nothingLost() {
#expect(BannerCenter.degradedPasteMessage(for: []) == nil)
#expect(BannerCenter.degradedPasteMessage(for: [.init(title: "A", attachments: 0)]) == nil)
}
@Test("Posting an empty loss list adds no row")
@MainActor
func postingNothing() {
let center = BannerCenter()
center.postDegradedPaste([])
#expect(center.losses.isEmpty)
center.postRefusedPaste(title: nil, stagedAt: "/tmp/staging/abc")
let error = try? #require(center.oneShots.first).error
#expect(error.map(BannerCenter.headline(for:)) == "Couldn't paste the item — the copied content is gone")
}
/// **The pivot, stated as a class change**: the degraded paste was a loss row because the items
/// landed and only their attachments did not. A refusal is *a write that did not happen*, which is
/// 02-architecture.md's own definition of a one-shot so it ranks with the true failures, carries
/// the error tone, and posts no loss row at all.
@Test("A refused paste is an error-tone one-shot, not a loss row")
@MainActor
func refusalIsAOneShotNotALossRow() {
let center = BannerCenter()
center.postRefusedPaste(title: "Fix login", stagedAt: "/tmp/staging/abc")
#expect(center.losses.isEmpty, "the degraded paste's loss row is retired")
#expect(center.signposts.isEmpty)
}
@Test("A degraded paste lands in the loss class, not the signpost class")
@MainActor
func postingLandsAsALossRow() {
// Settled 2026-07-28 (DESIGN/02-architecture.md § The banner surface, "Loss rows"): the
// degraded paste retoned from a signpost onto the new warning-tone loss class content
// that didn't arrive though nothing failed, ranking below the true failures and above the
// ambient notices rather than at the bottom of the strip.
let center = BannerCenter()
center.postDegradedPaste([.init(title: "Fix login", attachments: 3)])
#expect(center.losses.count == 1)
#expect(center.losses.first?.message == "Pasted 'Fix login' without its 3 attachments")
#expect(center.signposts.isEmpty, "the degraded paste no longer posts a signpost")
let rows = BannerCenter.rows(
lock: nil, breakage: nil, oneShots: [], losses: center.losses, suspension: nil, operations: []
lock: nil, breakage: nil, oneShots: center.oneShots, losses: [], suspension: nil, operations: []
)
#expect(rows.count == 1)
#expect(rows[0].tone == .warning)
#expect(rows[0].dismissID == center.losses.first?.id)
#expect(rows[0].tone == .error)
#expect(rows[0].dismissID == center.oneShots.first?.id)
}
/// The staging path is what the error names, so a bug report about a refusal has something to go
/// on the file that was not there.
@Test("The refusal names the staged path it could not find")
@MainActor
func namesTheStagedPath() {
let center = BannerCenter()
center.postRefusedPaste(title: "Fix login", stagedAt: "/tmp/staging/abc")
#expect(center.oneShots.first?.error.path == "/tmp/staging/abc")
#expect(center.oneShots.first?.error.reason == .clipboardContentGone)
#expect(center.oneShots.first?.error.operation == .paste(title: "Fix login"))
}
/// **The loss class survives the retirement** 02's warning-tone class still has live producers
/// (a Finder drop that skipped folders, the app's own relocation and repair notices); only the
/// degraded-paste row left it.
@Test("The loss class still has its other producers")
@MainActor
func theLossClassSurvives() {
let center = BannerCenter()
center.postSkippedFolders(count: 2)
#expect(center.losses.count == 1)
#expect(center.losses.first?.message == "Folders can't be attached — 2 skipped")
}
}
+14 -7
View File
@@ -757,10 +757,15 @@ struct LooseFilePasteTests {
#expect(try BoardLoader.load(boardRoot: destination.root).looseCardFiles.isEmpty)
}
/// The staging-less fallback carries only `index.md`, so there is nothing to normalize and the
/// normalization must not invent an `attachments/` for a card that has none.
@Test("A degraded paste normalizes nothing and mints no attachments folder")
func degradedPasteIsUnaffected() async throws {
/// **A refused paste normalizes nothing, because it materializes nothing** (04-interactions.md
/// Clipboard, re-ruled 2026-07-29 refuse, never degrade).
///
/// This was the degraded fallback's normalization case: the fallback carried only `index.md`, so the
/// claim was that normalization must not invent an `attachments/` for a card that had none. With the
/// fallback retired the claim gets stronger and simpler there is no arrival to normalize at all,
/// and the destination is exactly what it was.
@Test("A refused paste normalizes nothing because nothing arrives")
func aRefusedPasteNormalizesNothing() async throws {
let harness = try ClipboardHarness(fixture: try makeLooseFileClipboardBoard())
defer { harness.tearDown() }
let destination = try makePasteDestination()
@@ -776,10 +781,12 @@ struct LooseFilePasteTests {
}
target.select([ItemID(rawValue: Ident.lane4)], in: .board)
let before = try destination.entryNames(Ident.lane4).sorted()
await harness.clipboard.paste(into: target)?.value
let arrived = try arrivedCard(in: destination)
#expect(!destination.exists("\(Ident.lane4)/\(arrived)/attachments"))
#expect(try destination.entryNames("\(Ident.lane4)/\(arrived)") == ["index.md"])
#expect(try destination.entryNames(Ident.lane4).sorted() == before, "no card arrived")
#expect(target.banners.oneShots.count == 1, "and the refusal said so")
// The resident is untouched no attachments folder was invented anywhere in the lane.
#expect(try destination.entryNames("\(Ident.lane4)/\(Ident.indexless)") == ["index.md"])
}
}
+113 -125
View File
@@ -528,14 +528,34 @@ struct PasteCutTests {
}
}
// MARK: - The staging-less fallback
// MARK: - Refuse, never degrade
/// **04-interactions.md Clipboard, re-ruled 2026-07-29** Finder's invariant adopted:
///
/// > A paste whose staged snapshot is missing or unreadable refuses loudly never degrades
/// > the paste produces **nothing**, and a one-shot failure banner names it from the manifest's
/// > metadata. An item arrives **whole index, attachments, loose files, and comments when they ship
/// > or not at all** The refusal is transactional all-or-nothing for the whole paste.
///
/// These are the former `PasteFallbackTests`, turned around: every case that used to assert an item
/// materialized from the manifest's embedded `index.md` now asserts that **nothing** was written and a
/// failure banner names the entry. The manifest still embeds the text it is what names the entry in
/// the sentence below it is simply never a materialization source.
@MainActor
@Suite("Paste ▸ the staging-less fallback")
struct PasteFallbackTests {
@Suite("Paste ▸ refuse, never degrade")
struct PasteRefusalTests {
@Test("A missing snapshot falls back to the embedded index.md, byte-faithfully")
func fallbackWritesTheSourceBytes() async throws {
/// Drops the staged tree the way the world does: a sweep that ran early, an unreadable container,
/// a full disk mid-copy.
private func loseTheSnapshot(_ harness: ClipboardHarness) throws {
let copyID = try #require(harness.clipboard.payload?.copyID)
try FileManager.default.removeItem(
at: harness.staging.appendingPathComponent(copyID, isDirectory: true)
)
}
@Test("A missing snapshot writes nothing at all")
func aMissingSnapshotWritesNothing() async throws {
let harness = try makeClipboardHarness()
defer { harness.tearDown() }
let destination = try makeDestination()
@@ -545,31 +565,21 @@ struct PasteFallbackTests {
harness.store.select([clipboardCard1], in: .board)
harness.clipboard.copy(from: harness.store)
await harness.clipboard.stagingSettled()
// The snapshot goes a swept tree, a full disk, an unreadable container.
let copyID = try #require(harness.clipboard.payload?.copyID)
try FileManager.default.removeItem(
at: harness.staging.appendingPathComponent(copyID, isDirectory: true)
)
try loseTheSnapshot(harness)
target.select([destinationLane], in: .board)
await harness.clipboard.paste(into: target)?.value
let arrived = try #require(try pastedIDs(destinationLane, in: destination).last)
let document = try FrontmatterDocument.parse(destination.indexText("\(Ident.lane4)/\(arrived)"))
#expect(document.title.value == "First")
// Content intact: unknown keys, the comment's key, and the body all survived.
#expect(document.value(for: "project") != nil)
#expect(document.value(for: "labels") != nil)
#expect(document.body.contains("First body — with *markdown*"))
// `created` kept, fresh `order`.
#expect(document.created.value == ISO8601DateFormatter().date(from: "2026-01-01T09:00:00Z"))
#expect(document.order.value != 1024)
// Attachments absent which is exactly what the banner is about to say.
#expect(!destination.exists("\(Ident.lane4)/\(arrived)/attachments"))
#expect(
try pastedTitles(destinationLane, in: destination) == ["Resident"],
"the destination holds exactly what it held before"
)
}
@Test("A degraded paste banners, naming exactly what was lost")
func fallbackBanners() async throws {
/// 04's own example sentence, end to end: the entry is named from the manifest's metadata, which is
/// the whole reason the embedded `index.md` is still carried.
@Test("The refusal banners as a failure, naming the entry from the manifest")
func theRefusalBanners() async throws {
let harness = try makeClipboardHarness()
defer { harness.tearDown() }
let destination = try makeDestination()
@@ -579,43 +589,43 @@ struct PasteFallbackTests {
harness.store.select([clipboardCard1], in: .board)
harness.clipboard.copy(from: harness.store)
await harness.clipboard.stagingSettled()
let copyID = try #require(harness.clipboard.payload?.copyID)
try FileManager.default.removeItem(
at: harness.staging.appendingPathComponent(copyID, isDirectory: true)
)
try loseTheSnapshot(harness)
target.select([destinationLane], in: .board)
await harness.clipboard.paste(into: target)?.value
#expect(target.banners.losses.map(\.message) == ["Pasted 'First' without its 2 attachments"])
#expect(target.banners.losses.isEmpty, "the degraded paste's loss row is retired")
#expect(target.banners.oneShots.count == 1)
let error = try #require(target.banners.oneShots.first).error
#expect(BannerCenter.headline(for: error) == "Couldn't paste 'First' — the copied content is gone")
}
@Test("A fallback that lost nothing says nothing")
func fallbackWithoutAttachmentsIsSilent() async throws {
/// **The attachment-less case refuses too**, which is the pivot at its sharpest: under the degraded
/// rule this entry pasted *silently* its content was intact and it had no attachments to lose, so
/// nothing was reported. Refuse-don't-degrade does not ask what would have been lost; the bytes the
/// paste was to reproduce are gone, so there is nothing honest to write.
@Test("An entry with no attachments refuses just the same")
func anAttachmentLessEntryRefusesToo() async throws {
let harness = try makeClipboardHarness()
defer { harness.tearDown() }
let destination = try makeDestination()
defer { destination.tearDown() }
let target = try BoardStore(rootURL: destination.root)
// `card2` has no attachments, so a fallback loses nothing at all.
harness.store.select([clipboardCard2], in: .board)
harness.clipboard.copy(from: harness.store)
await harness.clipboard.stagingSettled()
let copyID = try #require(harness.clipboard.payload?.copyID)
try FileManager.default.removeItem(
at: harness.staging.appendingPathComponent(copyID, isDirectory: true)
)
try loseTheSnapshot(harness)
target.select([destinationLane], in: .board)
await harness.clipboard.paste(into: target)?.value
#expect(try pastedTitles(destinationLane, in: destination) == ["Resident", "Second"])
#expect(target.banners.losses.isEmpty)
#expect(try pastedTitles(destinationLane, in: destination) == ["Resident"])
#expect(target.banners.oneShots.count == 1)
}
@Test("A lane's fallback materializes its embedded cards")
func laneFallbackCarriesItsCards() async throws {
@Test("A lane payload refuses whole — no lane, no cards")
func aLanePayloadRefusesWhole() async throws {
let harness = try makeClipboardHarness()
defer { harness.tearDown() }
let destination = try makeDestination()
@@ -625,115 +635,93 @@ struct PasteFallbackTests {
harness.store.select([clipboardLane1], in: .board)
harness.clipboard.copy(from: harness.store)
await harness.clipboard.stagingSettled()
let copyID = try #require(harness.clipboard.payload?.copyID)
try FileManager.default.removeItem(
at: harness.staging.appendingPathComponent(copyID, isDirectory: true)
)
let before = try pasted(destination).lanes.map(\.id)
try loseTheSnapshot(harness)
await harness.clipboard.paste(into: target)?.value
let arrived = try #require(try pasted(destination).lanes.last)
#expect(arrived.title.value == "Todo")
// Both of the lane's cards.
#expect(arrived.cards.count == 2)
#expect(Set(arrived.cards.compactMap(\.title.value)) == ["First", "Second"])
#expect(target.banners.losses.map(\.message) == ["Pasted 'Todo' without its 2 attachments"])
#expect(try pasted(destination).lanes.map(\.id) == before, "the strip is untouched")
let error = try #require(target.banners.oneShots.first).error
#expect(BannerCenter.headline(for: error) == "Couldn't paste 'Todo' — the copied content is gone")
}
@Test("A trash-sourced fallback materializes an ordinary card — there is no key to strip")
func trashedFallbackIsOrdinary() async throws {
/// **All-or-nothing for the whole paste** the transactional half of the ruling, which the former
/// mixed path is exactly what retired: one entry's snapshot going missing used to leave its
/// siblings arriving whole beside a hollowed copy of it. Now the gesture refuses as a unit.
@Test("One missing snapshot refuses the whole multi-entry paste")
func oneMissingEntryRefusesTheWholePaste() async throws {
let harness = try makeClipboardHarness()
defer { harness.tearDown() }
let destination = try makeDestination()
defer { destination.tearDown() }
let target = try BoardStore(rootURL: destination.root)
harness.store.transient.isTrashVisible = true
harness.store.select([clipboardCard3], in: .trash)
harness.store.select([clipboardCard1, clipboardCard2], in: .board)
harness.clipboard.copy(from: harness.store)
await harness.clipboard.stagingSettled()
// Only the *first* entry's tree is removed; the second is staged and perfectly pasteable.
let copyID = try #require(harness.clipboard.payload?.copyID)
try FileManager.default.removeItem(
at: harness.staging.appendingPathComponent(copyID, isDirectory: true)
at: harness.staging
.appendingPathComponent(copyID, isDirectory: true)
.appendingPathComponent(Ident.card1, isDirectory: true)
)
target.select([destinationLane], in: .board)
await harness.clipboard.paste(into: target)?.value
#expect(try pastedTitles(destinationLane, in: destination) == ["Resident", "Trashed"])
}
}
// MARK: - BoardWriter.materializeItem
@Suite("BoardWriter ▸ materializeItem")
struct MaterializeItemTests {
@Test("The supplied bytes land verbatim but for the rewritten order and stamps")
func writesTheSuppliedBytes() throws {
let fixture = try WriterFixture()
defer { fixture.tearDown() }
try fixture.item("", Item.board)
try fixture.item(Ident.lane1, Item.rich(order: "1024", title: "Todo"))
let id = try BoardWriter.materializeItem(
inParent: fixture.url(Ident.lane1),
indexText: Item.rich(order: "9999", title: "Pasted"),
order: 512
#expect(
try pastedTitles(destinationLane, in: destination) == ["Resident"],
"not even the entry that could have arrived whole"
)
let document = try FrontmatterDocument.parse(fixture.indexText("\(Ident.lane1)/\(id.rawValue)"))
#expect(document.title.value == "Pasted")
#expect(document.order.value == 512)
#expect(document.value(for: "project") != nil)
#expect(document.value(for: "labels") != nil)
// The app-write stamps: `modified` set, `modified-by` cleared.
#expect(document.modified.value != ISO8601DateFormatter().date(from: "2026-02-02T09:00:00Z"))
#expect(document.modifiedBy.isMissing)
// `created` untouched a paste is a fork.
#expect(document.created.value == ISO8601DateFormatter().date(from: "2026-01-01T09:00:00Z"))
#expect(target.banners.oneShots.count == 1, "one refusal for one gesture")
}
@Test("Children are materialized under fresh identities and never rewritten")
func childrenAreVerbatim() throws {
let fixture = try WriterFixture()
defer { fixture.tearDown() }
try fixture.item("", Item.board)
let id = try BoardWriter.materializeItem(
inParent: fixture.root,
indexText: Item.rich(order: "1024", title: "Lane"),
children: [Item.rich(order: "1024", title: "One"), Item.uneditable],
order: 1024
)
let lane = try #require(try BoardLoader.load(boardRoot: fixture.root).model.lanes.first)
#expect(lane.id == id)
#expect(lane.cards.count == 2)
// An uneditable child arrives exactly as it was the leniency `copyItem` extends below its
// root, applied here.
let names = try FileManager.default.contentsOfDirectory(atPath: fixture.url(id.rawValue).path)
.filter { $0 != "index.md" }
let odd = try #require(names.first { name in
(try? fixture.indexText("\(id.rawValue)/\(name)")) == Item.uneditable
})
#expect(try fixture.indexText("\(id.rawValue)/\(odd)") == Item.uneditable)
/// **A refusal costs the user their content *and* nothing else** the destination's active search
/// survives it. "Any user-initiated creation on the board clears the query" (04 Search) is a rule
/// about creations, and a refused paste creates nothing.
@Test("A refused paste leaves the destination's search alone")
func aRefusalKeepsTheSearch() async throws {
let harness = try makeClipboardHarness()
defer { harness.tearDown() }
let destination = try makeDestination()
defer { destination.tearDown() }
let target = try BoardStore(rootURL: destination.root)
target.transient.searchQuery = "resident"
harness.store.select([clipboardCard1], in: .board)
harness.clipboard.copy(from: harness.store)
await harness.clipboard.stagingSettled()
try loseTheSnapshot(harness)
target.select([destinationLane], in: .board)
await harness.clipboard.paste(into: target)?.value
#expect(target.transient.searchQuery == "resident")
}
@Test("An unparseable root refuses and leaves nothing behind")
func unparseableRootLeavesNoResidue() throws {
let fixture = try WriterFixture()
defer { fixture.tearDown() }
try fixture.item("", Item.board)
let error = writeFailure {
_ = try BoardWriter.materializeItem(
inParent: fixture.root,
indexText: "no frontmatter here at all\n",
order: 1024
)
}
#expect(error != nil)
#expect(try fixture.entryNames("") == ["index.md"])
/// A cut whose staged snapshot is gone is a different story and stays one: an armed cut moves the
/// **originals**, which are real folders in the source board, so it never reads staging at all.
/// The refusal is the copy path's, and this pins that it did not spread.
@Test("An armed cut still moves its originals — it never reads staging")
func anArmedCutIsUnaffected() async throws {
let harness = try makeClipboardHarness()
defer { harness.tearDown() }
let destination = try makeDestination()
defer { destination.tearDown() }
let target = try BoardStore(rootURL: destination.root)
harness.store.select([clipboardCard1], in: .board)
harness.clipboard.cut(from: harness.store)
await harness.clipboard.stagingSettled()
try loseTheSnapshot(harness)
target.select([destinationLane], in: .board)
await harness.clipboard.paste(into: target)?.value
#expect(try pastedTitles(destinationLane, in: destination) == ["Resident", "First"])
#expect(target.banners.oneShots.isEmpty, "nothing failed — the folder moved")
#expect(harness.fixture.exists("\(Ident.lane1)/\(Ident.card1)") == false, "and it left the source")
}
}
+150
View File
@@ -528,6 +528,156 @@ struct TemplateEngineAtomicityTests {
}
}
// MARK: - The copy contract
/// **Instantiation is a copy transaction, and it severs tracker identity** the two 2026-07-29
/// rulings applied to the flow 01 names alongside paste and the -drag duplicate (01-storage-format.md
/// § Frontmatter's compound-operations clause; § Fractal layout Rules' item-level sever).
@Suite("TemplateEngine — the copy contract")
struct TemplateEngineCopyContractTests {
/// A template carrying a readable-but-uneditable card refuses the **whole** create, naming that
/// card, and leaves nothing where the user pointed the former root-strict/descendants-lenient
/// split would have made a board from it with one silently unstamped card inside.
@Test("An uneditable card in the template refuses the create, naming it")
func anUneditableCardRefusesTheCreate() throws {
let template = try FixtureTemplate()
defer { template.tearDown() }
try template.fixture.item(
"\(FixtureTemplate.name)/\(Ident.lane1)/\(Ident.card3)",
Item.uneditable
)
let destination = template.destination()
let failure = instantiationFailure {
try TemplateEngine.instantiate(template: try template.template(), to: destination, title: "Doomed")
}
guard case let .failed(write) = failure else {
Issue.record("expected an ordinary failure, got \(String(describing: failure))")
return
}
if case .uneditableFrontmatter = write.reason {} else {
Issue.record("expected the uneditable-shape refusal, got \(write.reason)")
}
#expect(write.operation == .createBoard, "the create is what refused")
#expect(
!FileManager.default.fileExists(atPath: destination.path),
"construct-then-clean: the partial destination goes with the refusal"
)
}
/// A template whose `.trash/` holds a broken card still instantiates: the preflight runs on the
/// **destination**, after the copy applied its exclusions, so a card that was never going to be
/// copied cannot refuse the create it has nothing to do with.
@Test("An uneditable card in the template's trash refuses nothing — it is never copied")
func anUneditableTrashCardIsIrrelevant() throws {
let template = try FixtureTemplate()
defer { template.tearDown() }
try template.fixture.item("\(FixtureTemplate.name)/.trash/\(Ident.card3)", Item.uneditable)
let destination = template.destination()
try TemplateEngine.instantiate(template: try template.template(), to: destination, title: "Fine")
#expect(FileManager.default.fileExists(atPath: destination.appendingPathComponent("index.md").path))
#expect(
!FileManager.default.fileExists(atPath: destination.appendingPathComponent(".trash").path),
"and the trash was excluded, as always"
)
}
/// **The tracker sever, at every level an instantiation materializes** board root, lane, and card.
/// A template can carry the keys in from the board it was saved from (Save as Template is a fork and
/// keeps them verbatim), and the board born from it must not claim those remote objects.
@Test("Instantiation drops the reserved tracker keys at every level")
func instantiationSeversTrackerIdentity() throws {
let fixture = try WriterFixture()
defer { fixture.tearDown() }
let name = "Tracked.kanban"
try fixture.item(name, """
---
schema: 1
title: Tracked Template
template: {order: 1}
project: lanework
remote: gitea#7
---
Blurb.
""")
try fixture.item("\(name)/\(Ident.lane1)", Item.tracked(order: "1024", title: "To Do", key: "remote-state"))
try fixture.item(
"\(name)/\(Ident.lane1)/\(Ident.card1)",
Item.tracked(order: "1024", title: "Starter", key: "remote")
)
let template: BoardTemplate = switch TemplateEngine.load(templateAt: fixture.url(name), origin: .user) {
case let .success(loaded): loaded
case let .failure(error): throw error
}
let destination = fixture.url("Born.kanban")
try TemplateEngine.instantiate(template: template, to: destination, title: "Born")
let board = try FrontmatterDocument.parse(String(
decoding: Data(contentsOf: destination.appendingPathComponent("index.md")), as: UTF8.self
))
#expect(board.value(for: "remote") == nil, "the board born today claims no remote object")
#expect(board.value(for: "project") != nil, "and every other unknown key rode along")
let lanes = ((try? BoardLoader.directoryCandidates(in: destination)) ?? [])
.filter { BoardLoader.isUUIDShaped($0.lastPathComponent) }
let lane = try FrontmatterDocument.parse(String(
decoding: Data(contentsOf: try #require(lanes.first).appendingPathComponent("index.md")), as: UTF8.self
))
#expect(lane.value(for: "remote-state") == nil)
let card = try FrontmatterDocument.parse(String(
decoding: Data(contentsOf: try #require(cardFolders(under: destination).first)
.appendingPathComponent("index.md")), as: UTF8.self
))
#expect(card.value(for: "remote") == nil)
#expect(card.value(for: "project") != nil)
}
/// **Save as Template is a whole-board fork and is exempt** (01 Identity lifecycle's carve-out):
/// it "carries them verbatim", GUIDs, timestamps and tracker keys alike, because a fork is a new
/// namespace rather than a second claimant inside one board. The sever belongs to *item-level*
/// copies, and this is the line between them.
@Test("Save as Template carries the tracker keys verbatim")
func saveAsTemplateIsExempt() throws {
let fixture = try WriterFixture()
defer { fixture.tearDown() }
try fixture.item("Board.kanban", """
---
schema: 1
title: Live Board
remote: gitea#7
---
Body.
""")
try fixture.item("Board.kanban/\(Ident.lane1)", Item.tracked(order: "1024", title: "To Do", key: "remote-state"))
try fixture.item(
"Board.kanban/\(Ident.lane1)/\(Ident.card1)",
Item.tracked(order: "1024", title: "Card", key: "remote")
)
let store = fixture.url("Store")
let saved = try TemplateEngine.saveAsTemplate(
boardAt: fixture.url("Board.kanban"), titled: "Live Board", into: store
)
let board = try FrontmatterDocument.parse(String(
decoding: Data(contentsOf: saved.appendingPathComponent("index.md")), as: UTF8.self
))
#expect(board.value(for: "remote") != nil, "a fork carries them verbatim")
let lane = try FrontmatterDocument.parse(String(
decoding: Data(contentsOf: saved.appendingPathComponent(Ident.lane1).appendingPathComponent("index.md")),
as: UTF8.self
))
#expect(lane.value(for: "remote-state") != nil)
}
}
// MARK: - Shared
/// Every card folder under an instantiated board `<root>/<lane>/<card>`, by the loader's own level
+71 -5
View File
@@ -390,7 +390,7 @@ struct PurgeTests {
let store = try BoardStore(rootURL: fixture.root)
store.select([card1], in: .board)
store.deleteImmediately([card1])
store.deleteImmediately([card1], in: .board)
#expect(!fixture.exists("\(Ident.lane1)/\(Ident.card1)"))
#expect(!fixture.exists(".trash/\(Ident.card1)"), "03 ▸ Trash: ⌥⌘⌫ skips the trash from anywhere")
@@ -404,7 +404,7 @@ struct PurgeTests {
let store = try BoardStore(rootURL: fixture.root)
store.select([trashed], in: .trash)
store.deleteImmediately([trashed])
store.deleteImmediately([trashed], in: .trash)
#expect(!fixture.exists(".trash/\(Ident.indexless)"))
#expect(fixture.exists(".trash/\(More.newer)"), "and only what it named")
@@ -417,7 +417,7 @@ struct PurgeTests {
let store = try BoardStore(rootURL: fixture.root)
store.select([lane3], in: .board)
store.deleteImmediately([lane3])
store.deleteImmediately([lane3], in: .board)
#expect(fixture.exists(Ident.lane3))
}
@@ -473,7 +473,7 @@ struct PurgeTests {
store.select([trashed], in: .trash)
store.deleteTrashCards([trashed])
store.deleteImmediately([newer])
store.deleteImmediately([newer], in: .trash)
store.emptyTrash()
// 13-native-undo.md Rules: "Permanently delete (Delete Immediately, Empty Trash)
@@ -745,7 +745,7 @@ struct TrashConfirmationsTests {
let pending = try #require(confirmations.pending)
#expect(pending.prompt.title == "Permanently delete \u{201C}Trashed\u{201D}?")
#expect(pending.action == .purge([trashed]))
#expect(pending.action == .purge([trashed], .trash))
// Nothing has happened yet the alert is what stands between the keystroke and the loss.
#expect(fixture.exists(".trash/\(Ident.indexless)"))
@@ -756,6 +756,72 @@ struct TrashConfirmationsTests {
confirmations.confirm(in: store)
}
/// **The card and lane context menus' -alternate** Delete Immediately, routed through
/// `requestBoardPurge` rather than through `requestPurge` (11-command-nexus.md Context menus'
/// Card and Lane rows: "Delete with Delete Immediately as its -alternate").
/// `purgeConfirmsThenActs`'s twin for the board side: same alert, same rule, a board card as the
/// target instead of a trash one.
@Test("The board-side ⌥-alternate raises the same alert, and purges the board card on confirm")
func boardPurgeConfirmsThenActs() throws {
let fixture = try makeBoard()
defer { fixture.tearDown() }
let store = try BoardStore(rootURL: fixture.root)
let confirmations = TrashConfirmations()
confirmations.requestBoardPurge(of: [card1], in: store)
let pending = try #require(confirmations.pending)
#expect(pending.prompt.title == "Permanently delete \u{201C}First\u{201D}?")
#expect(pending.action == .purge([card1], .board))
// Nothing has happened yet same alert, same rule.
#expect(fixture.exists("\(Ident.lane1)/\(Ident.card1)"))
confirmations.confirm(in: store)
#expect(!fixture.exists("\(Ident.lane1)/\(Ident.card1)"))
#expect(!fixture.exists(".trash/\(Ident.card1)"), "skips the trash — purged, not moved")
#expect(confirmations.pending == nil)
}
/// A context menu names its target by where it was invoked, so a card row's Delete Immediately
/// must purge the *clicked* card even while a different card is selected `TrashMenuValidation
/// Tests.contextMenuDeleteIgnoresTheSelection`'s claim, mirrored onto the board side.
@Test("The board-side ⌥-alternate acts on its own target, not the standing selection")
func boardPurgeIgnoresTheSelection() throws {
let fixture = try makeBoard()
defer { fixture.tearDown() }
let store = try BoardStore(rootURL: fixture.root)
let confirmations = TrashConfirmations()
// A right-click on `card1` without first selecting it must still purge `card1`, never the
// card the standing selection happens to hold (`CardFaceView.targetIDs`'s targeting rule).
store.select([card2], in: .board)
confirmations.requestBoardPurge(of: [card1], in: store)
let pending = try #require(confirmations.pending)
#expect(pending.action == .purge([card1], .board))
confirmations.confirm(in: store)
#expect(!fixture.exists("\(Ident.lane1)/\(Ident.card1)"), "the clicked card is gone")
#expect(fixture.exists("\(Ident.lane1)/\(Ident.card2)"), "the selected card was never the subject")
}
/// `TrashModel.canDeleteImmediately` is cards only (`TrashValidationTests
/// .canDeleteImmediatelyIsCardsOnly`: "a lane's delete is physical already there is nothing for
/// 'skip the trash' to mean on one"), and the lane row's alternate inherits that unchanged: it is
/// wired per 11-command-nexus.md's Lane row, but presently inert on a lane-only target the same
/// posture File Delete Immediately already takes on a lane-only selection.
@Test("A lane-only target raises no prompt — the alternate is still cards only")
func boardPurgeIsStillCardsOnlyForALaneTarget() throws {
let fixture = try makeBoard()
defer { fixture.tearDown() }
let store = try BoardStore(rootURL: fixture.root)
let confirmations = TrashConfirmations()
confirmations.requestBoardPurge(of: [lane1], in: store)
#expect(confirmations.pending == nil)
#expect(fixture.exists(Ident.lane1))
}
/// 03-board-ui.md § Trash: "on a trash card, Delete (/) is permanent Both confirm exactly
/// where the loss is real."
@Test("The trash's own Delete confirms; the board's goes straight through")
+86 -1
View File
@@ -487,6 +487,91 @@ struct MoveUndoTests {
#expect(try document(fixture, Ident.lane1).order.value == moved)
}
/// **The inverses conform to the container-change predicate** (01-storage-format.md
/// § Frontmatter `modified`'s scope, refined 2026-07-30) the m8 conformance check, stated at
/// the level the rule is about: an inverse is an ordinary app-mediated write, so it is subject to
/// the *same* predicate as the gesture it inverts, not to a rule of its own.
///
/// Three claims in one round trip, because they are one claim: the undo of a within-lane reorder is
/// itself a within-lane reorder and rewrites only `order`; the undo of a cross-lane move is itself a
/// cross-lane move and stamps; and **no trash-specific branch exists in either direction** the
/// trash round trip stamps for the same reason the cross-lane one does.
///
/// It reads `modified-by` rather than `modified`, deliberately: `untouchedLines` filters the whole
/// `modified*` family precisely because a content write is *expected* to move it, so the foreign
/// stamp's survival is the assertion with a sharp edge it survives an order-only rewrite and is
/// cleared by a content one, and `Item.rich` plants one on every fixture card for exactly this.
@Test("An inverse stamps only when it changes a container")
func inversesFollowTheContainerPredicate() throws {
let fixture = try makeBoard()
defer { fixture.tearDown() }
let (store, history) = try makeStore(fixture)
// Within-lane, there and back: nothing on either leg is a content write.
store.moveCards([card1], toLane: lane1, at: 2)
#expect(try document(fixture, card1Path).rawValue(for: FrontmatterKeys.modifiedBy) == "claude")
history.undo()
#expect(
try document(fixture, card1Path).rawValue(for: FrontmatterKeys.modifiedBy) == "claude",
"undoing a reorder is a reorder — order-only, both ways"
)
// Cross-lane, there and back: both legs change the container, so both stamp.
store.moveCards([card2], toLane: lane2, at: 0)
#expect(try document(fixture, "\(Ident.lane2)/\(Ident.card2)").rawValue(for: FrontmatterKeys.modifiedBy) == nil)
// Re-planted by hand, standing in for an agent that stamped the card in its new lane the
// inverse has to clear it again, because moving back is itself a container change.
try BoardWriter.updateIndex(
inItemFolder: fixture.url("\(Ident.lane2)/\(Ident.card2)"), operation: .style(title: nil)
) { $0.set(FrontmatterKeys.modifiedBy, to: .string("claude")) }
history.undo()
#expect(
try document(fixture, card2Path).rawValue(for: FrontmatterKeys.modifiedBy) == nil,
"undoing a cross-lane move is a cross-lane move — it stamps"
)
}
/// The lane half of the same claim: a lane's container is the board root and never changes, so a
/// lane drag and its inverse are both order-only.
@Test("A lane reorder and its inverse are both order-only")
func laneInversesAreOrderOnly() throws {
let fixture = try makeBoard()
defer { fixture.tearDown() }
let (store, history) = try makeStore(fixture)
store.moveLane(lane1, toIndex: 1)
#expect(try document(fixture, Ident.lane1).rawValue(for: FrontmatterKeys.modifiedBy) == "claude")
history.undo()
#expect(try document(fixture, Ident.lane1).rawValue(for: FrontmatterKeys.modifiedBy) == "claude")
history.redo()
#expect(try document(fixture, Ident.lane1).rawValue(for: FrontmatterKeys.modifiedBy) == "claude")
}
/// The trash round trip, from the undo stack rather than the Writer: the delete stamps and its
/// inverse the move back out stamps too. **Neither is a special case**; both are container
/// changes, which is the whole of the refinement.
@Test("A delete and its inverse both stamp, with no trash branch")
func theTrashRoundTripStampsBothWays() throws {
let fixture = try makeBoard()
defer { fixture.tearDown() }
let (store, history) = try makeStore(fixture)
store.select([card1], in: .board)
store.deleteSelection()
#expect(fixture.exists(".trash/\(Ident.card1)"))
#expect(try document(fixture, ".trash/\(Ident.card1)").rawValue(for: FrontmatterKeys.modifiedBy) == nil)
try BoardWriter.updateIndex(
inItemFolder: fixture.url(".trash/\(Ident.card1)"), operation: .style(title: nil)
) { $0.set(FrontmatterKeys.modifiedBy, to: .string("claude")) }
history.undo()
#expect(fixture.exists(card1Path))
#expect(
try document(fixture, card1Path).rawValue(for: FrontmatterKeys.modifiedBy) == nil,
"restoring out of the trash is a container change and clears the stamp"
)
}
@Test("⌥⌘↓ undoes the whole permutation, siblings included")
func sortRoundTrip() throws {
let fixture = try makeBoard()
@@ -735,7 +820,7 @@ struct NotUndoableTests {
let armed = try #require(history.undoActionName)
store.select([trashed], in: .trash)
store.deleteImmediately([trashed])
store.deleteImmediately([trashed], in: .trash)
#expect(fixture.exists(trashedPath) == false)
#expect(store.purgeIsUnrecoverable)
+200
View File
@@ -393,3 +393,203 @@ struct WriteFidelityCompositeTests {
#expect(forkedThird.body == "Third body.\n")
}
}
// MARK: - The container-change predicate
/// **01-storage-format.md § Frontmatter `modified`'s scope** ruled 2026-07-29 as
/// moves-don't-stamp, **refined 2026-07-30** to one container-change predicate:
///
/// > a reorder within the item's container (a card among its lane's siblings, a lane among the
/// > board's lanes) and a renumber's whole-lane rescale rewrite `index.md` without touching content:
/// > no stamp, and no `modified-by` clear **A move that changes the item's container stamps both**:
/// > a cross-lane move, a cross-board arrival, and the trash move.
///
/// The pairing is the thing these tests are really pinning: `modified` and `modified-by` move
/// together, always, because "attribution can't change when content didn't". So every case below
/// asserts both keys, and the fixtures deliberately carry a foreign `modified-by: claude` the key
/// whose survival is the only visible difference between an order-only rewrite and a content one.
///
/// **There is deliberately no trash case in the implementation**, and that is what
/// `theTrashMoveStampsBecauseEveryContainerChangeDoes` exists to state from the outside: the trash
/// move stamps, and it does so through the same predicate as a cross-lane move rather than through a
/// branch of its own.
struct WriteFidelityStampingTests {
/// The prior stamps every fixture below starts from `Item.rich`'s own, so a test asserting
/// "unchanged" is asserting against a real value that a stamp would visibly replace.
private static let priorModified = "2026-02-02T09:00:00Z"
private func stamps(_ fixture: WriterFixture, _ path: String) throws -> (modified: String?, modifiedBy: String?) {
let document = try FrontmatterDocument.parse(fixture.indexText(path))
return (document.rawValue(for: FrontmatterKeys.modified), document.rawValue(for: FrontmatterKeys.modifiedBy))
}
private func twoLaneBoard() throws -> WriterFixture {
let fixture = try WriterFixture()
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: "First"))
try fixture.item("\(Ident.lane1)/\(Ident.card2)", Item.rich(order: "2048", title: "Second"))
try fixture.item(Ident.lane2, Item.rich(order: "2048", title: "Doing"))
return fixture
}
/// A card dropped back into its own lane `moveItem`'s same-parent degenerate path, which is
/// every within-lane drag, every / sort step, and every inverse of one.
@Test("A card reordered among its lane's siblings rewrites only order")
func aWithinLaneReorderRewritesOnlyOrder() throws {
let fixture = try twoLaneBoard()
defer { fixture.tearDown() }
_ = try BoardWriter.moveItem(
at: fixture.url("\(Ident.lane1)/\(Ident.card1)"),
toParent: fixture.url(Ident.lane1),
sourceBoardRoot: fixture.root,
destinationBoardRoot: fixture.root,
order: 3072
)
let after = try stamps(fixture, "\(Ident.lane1)/\(Ident.card1)")
#expect(after.modified == Self.priorModified, "a reorder is not a content write")
#expect(after.modifiedBy == "claude", "and attribution can't change when content didn't")
let document = try FrontmatterDocument.parse(fixture.indexText("\(Ident.lane1)/\(Ident.card1)"))
#expect(document.order.value == 3072, "the one key a reorder owns did move")
}
/// A lane's parent is the board root and nothing else, so *every* lane reorder is
/// within-container /, the strip drag, and their inverses alike.
@Test("A lane reordered on the board rewrites only order")
func aLaneReorderRewritesOnlyOrder() throws {
let fixture = try twoLaneBoard()
defer { fixture.tearDown() }
_ = try BoardWriter.moveItem(
at: fixture.url(Ident.lane2),
toParent: fixture.root,
sourceBoardRoot: fixture.root,
destinationBoardRoot: fixture.root,
order: 512
)
let after = try stamps(fixture, Ident.lane2)
#expect(after.modified == Self.priorModified)
#expect(after.modifiedBy == "claude")
#expect(try FrontmatterDocument.parse(fixture.indexText(Ident.lane2)).order.value == 512)
}
/// The renumber rescale 01 § Ordering, verbatim: "order-only rewrites, so no `modified` stamp
/// and no `modified-by` clear". Every sibling in the lane is rewritten, and not one of them is
/// stamped, which is what keeps a midpoint exhaustion from reading as a lane's worth of edits.
@Test("A renumber rescale stamps nothing, on any sibling")
func aRenumberRescaleStampsNothing() throws {
let fixture = try twoLaneBoard()
defer { fixture.tearDown() }
try BoardWriter.renumberVisibleChildren(of: fixture.url(Ident.lane1))
for path in ["\(Ident.lane1)/\(Ident.card1)", "\(Ident.lane1)/\(Ident.card2)"] {
let after = try stamps(fixture, path)
#expect(after.modified == Self.priorModified, "\(path) was stamped by a rescale")
#expect(after.modifiedBy == "claude", "\(path) lost its attribution to a rescale")
}
#expect(try FrontmatterDocument.parse(fixture.indexText("\(Ident.lane1)/\(Ident.card1)")).order.value == 1024)
#expect(try FrontmatterDocument.parse(fixture.indexText("\(Ident.lane1)/\(Ident.card2)")).order.value == 2048)
}
/// The other side of the predicate: which lane a card lives in is *state*, so crossing lanes is a
/// content write and stamps both keys.
@Test("A cross-lane move stamps modified and clears modified-by")
func aCrossLaneMoveStamps() throws {
let fixture = try twoLaneBoard()
defer { fixture.tearDown() }
_ = try BoardWriter.moveItem(
at: fixture.url("\(Ident.lane1)/\(Ident.card1)"),
toParent: fixture.url(Ident.lane2),
sourceBoardRoot: fixture.root,
destinationBoardRoot: fixture.root,
order: 1024
)
let after = try stamps(fixture, "\(Ident.lane2)/\(Ident.card1)")
#expect(after.modified != Self.priorModified, "a container change is a content write")
#expect(after.modifiedBy == nil, "and clears the foreign stamp like any app write")
}
/// **No trash special case anywhere.** The delete stamps, the restore stamps, and both do it
/// through the container predicate rather than through a rule of their own which is why this
/// test asserts the same two facts as `aCrossLaneMoveStamps` and nothing extra.
@Test("The trash move stamps because every container change does — in and out")
func theTrashMoveStampsBecauseEveryContainerChangeDoes() throws {
let fixture = try twoLaneBoard()
defer { fixture.tearDown() }
try BoardWriter.deleteCardToTrash(
at: fixture.url("\(Ident.lane1)/\(Ident.card1)"), inBoard: fixture.root, order: 1024
)
let trashed = try stamps(fixture, ".trash/\(Ident.card1)")
#expect(trashed.modified != Self.priorModified, "into the trash is a container change")
#expect(trashed.modifiedBy == nil)
// And out again. `modified-by` is re-planted by hand first, standing in for the agent that
// re-stamped the card while it sat in the trash: the restore has to clear it again.
try BoardWriter.updateIndex(
inItemFolder: fixture.url(".trash/\(Ident.card1)"), operation: .style(title: nil)
) { $0.set(FrontmatterKeys.modifiedBy, to: .string("claude")) }
_ = try BoardWriter.moveItem(
at: fixture.url(".trash/\(Ident.card1)"),
toParent: fixture.url(Ident.lane2),
sourceBoardRoot: fixture.root,
destinationBoardRoot: fixture.root,
order: 4096
)
let restored = try stamps(fixture, "\(Ident.lane2)/\(Ident.card1)")
#expect(restored.modifiedBy == nil, "out of the trash is a container change too")
}
/// A cross-board arrival changes the container as surely as a cross-lane move does, and the
/// import boundary's remint does not change that: the arrived file is stamped either way.
@Test("A cross-board arrival stamps")
func aCrossBoardArrivalStamps() throws {
let source = try twoLaneBoard()
defer { source.tearDown() }
let destination = try WriterFixture()
defer { destination.tearDown() }
try destination.item("", Item.board)
try destination.item(Ident.lane3, Item.rich(order: "1024", title: "Elsewhere"))
let result = try BoardWriter.moveItem(
at: source.url("\(Ident.lane1)/\(Ident.card1)"),
toParent: destination.url(Ident.lane3),
sourceBoardRoot: source.root,
destinationBoardRoot: destination.root,
order: 1024
)
let after = try stamps(destination, "\(Ident.lane3)/\(result.id.rawValue)")
#expect(after.modified != Self.priorModified)
#expect(after.modifiedBy == nil)
}
/// The predicate as a pure value one exhaustive statement of which operations are order-only,
/// so a new `WriteOperation` cannot quietly join or leave the class. **`.reorder` and
/// `.renumberChildren`, and nothing else**; `.delete` and `.move` are named explicitly because
/// they are the two a "moves don't stamp" reading would have put on the wrong side.
@Test("Only reorder and renumber are order-only")
func theOrderOnlyClassIsExactlyTwoOperations() {
#expect(WriteOperation.reorder(title: nil).rewritesOrderOnly)
#expect(WriteOperation.renumberChildren.rewritesOrderOnly)
for operation: WriteOperation in [
.createBoard, .createLane, .createCard, .move(title: nil), .copy(title: nil),
.paste(title: nil), .delete(title: nil), .purge(title: nil), .migrateTombstone(title: nil),
.style(title: nil), .resize(title: nil), .rename(title: nil), .duplicateBoard(title: nil),
.saveAsTemplate(title: nil), .importAttachment(filename: "a"), .listAttachments,
.removeAttachment(filename: "a"), .relocateLooseFile(filename: "a"), .agentGuide,
.displaceClaimedName(name: ".trash"), .repairDuplicateID(title: nil),
.toggleTask(title: nil), .editBody(title: nil), .rawSource(title: nil),
] {
#expect(operation.rewritesOrderOnly == false, "\(operation) should be a content write")
}
}
}
+21
View File
@@ -211,6 +211,27 @@ enum Item {
/// A whole-frontmatter flow mapping carrying a `modified-by`: readable, uneditable, and so
/// left byte-verbatim by a copy stale attribution included.
static let uneditable = "---\n{schema: 1, order: 1024, title: Odd, modified-by: claude}\n---\nodd body\n"
/// An item carrying one of the **reserved tracker keys** `remote` on a board or card,
/// `remote-state` on a lane (01-storage-format.md § Enhanced schema) beside an ordinary unknown
/// key, so a copy's tracker sever can be told apart from unknown-key preservation breaking.
///
/// Nothing in this version reads the keys; what the suites pin is that an **item-level copy drops
/// them** (ruled 2026-07-29) while a whole-board fork carries them verbatim.
static func tracked(order: String, title: String, key: String) -> String {
"""
---
schema: 1
title: \(title)
order: \(order)
project: lanework # agent overlay
\(key): gitea#42
created: 2026-01-01T09:00:00Z
---
\(title) body.
"""
}
}
// MARK: - Failure assertion