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