Align the identity predicate with the ratified shape-only rule

Accept liberally, emit conservatively (764a4d4): the gate is 8-4-4-4-12
hex in any case and any UUID version — uuidgen and UUID().uuidString
print uppercase, and a strict lowercase gate would silently stray an
agent's standard-tool card. Identity comparison is UUID-value equality
everywhere: ItemID keeps its byte-faithful rawValue but equates and
hashes on the lowercased canonical form, and the writer's
import-boundary collision check canonicalizes, so a same-UUID arrival
spelled in another case remints instead of slipping past. The app still
mints only lowercase v4 and never renames to canonicalize.

Full suite 342 tests in 63 suites green. Two findings filed.

Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
This commit is contained in:
2026-07-26 20:53:38 -04:00
parent 1d7449e49a
commit 4418b7f981
6 changed files with 301 additions and 51 deletions
+60
View File
@@ -1014,6 +1014,66 @@ struct BoardWriterMoveTests {
#expect(try fixture.indexData("B.kanban/\(Ident.lane3)/\(Ident.card1)") == twinBefore)
}
/// **Identity comparison is UUID-value equality, never string equality** (01-storage-format.md
/// § Fractal layout Rules, settled): the destination board already holds the arriving UUID
/// spelled in *uppercase* an agent's `uuidgen` card so the two are one identity and the
/// import boundary must remint. A verbatim string set would sail straight past this and leave
/// a duplicate UUID in the board.
@Test func aCollisionSpelledInADifferentCaseIsStillOneIdentityAndRemints() throws {
let fixture = try WriterFixture()
defer { fixture.tearDown() }
try boardA(fixture)
try boardB(fixture)
let arriving = "abcdef01-2345-6789-abcd-ef0123456789"
let twin = arriving.uppercased() // The same UUID, as `uuidgen` would have printed it.
try fixture.item("A.kanban/\(Ident.lane1)/\(arriving)", Item.rich(order: "3072", title: "Card One"))
try fixture.item("B.kanban/\(Ident.lane3)/\(twin)", Item.rich(order: "2048", title: "Stale Twin"))
let twinBefore = try fixture.indexData("B.kanban/\(Ident.lane3)/\(twin)")
let result = try move(
fixture, "A.kanban/\(Ident.lane1)/\(arriving)",
to: "B.kanban/\(Ident.lane4)", into: "B.kanban"
)
let minted = result.id.rawValue
#expect(BoardLoader.isUUIDShaped(minted))
#expect(ItemID(rawValue: minted) != ItemID(rawValue: arriving))
#expect(ItemID(rawValue: minted) != ItemID(rawValue: twin))
#expect(result.reminted == [MoveResult.Remint(from: ItemID(rawValue: arriving), to: ItemID(rawValue: minted))])
#expect(try FrontmatterDocument.parse(fixture.indexText("B.kanban/\(Ident.lane4)/\(minted)")).title
== .valid("Card One"))
// The resident twin is untouched, and the source left as for any move.
#expect(try fixture.indexData("B.kanban/\(Ident.lane3)/\(twin)") == twinBefore)
#expect(!fixture.exists("A.kanban/\(Ident.lane1)/\(arriving)"))
}
/// The same rule one level down: a lane arrives carrying a card whose UUID the destination
/// board already holds under a different case-spelling that card, and only that card, is
/// reminted.
@Test func aLaneMoveRemintsAChildCollidingOnlyByCaseSpelling() throws {
let fixture = try WriterFixture()
defer { fixture.tearDown() }
try boardA(fixture)
try boardB(fixture)
let arriving = "abcdef01-2345-6789-abcd-ef0123456789"
let twin = arriving.uppercased()
try fixture.item("A.kanban/\(Ident.lane1)/\(arriving)", Item.rich(order: "3072", title: "Colliding"))
try fixture.item("B.kanban/\(Ident.lane3)/\(twin)", Item.rich(order: "2048", title: "B's Own"))
let result = try move(fixture, "A.kanban/\(Ident.lane1)", to: "B.kanban", into: "B.kanban")
#expect(result.id.rawValue == Ident.lane1)
#expect(result.reminted.map(\.from.rawValue) == [arriving])
let minted = try #require(result.reminted.first?.to.rawValue)
#expect(fixture.exists("B.kanban/\(Ident.lane1)/\(minted)"))
#expect(!fixture.exists("B.kanban/\(Ident.lane1)/\(arriving)"))
// The card that collided with nothing kept its identity.
#expect(fixture.exists("B.kanban/\(Ident.lane1)/\(Ident.card1)"))
}
/// The collision sitting in the very lane being dropped into the case a move-then-rename
/// could not repair, because the plain move would fail on the existing name.
@Test func aCollisionInTheDestinationParentItselfIsStillReminted() throws {