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:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user