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
+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)
}
}