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
+32 -16
View File
@@ -9,8 +9,9 @@ import os
///
/// Level is position: root `index.md` board, depth-1 folders lanes, depth-2 folders
/// cards. **Name shape gates level detection** (01-storage-format.md § Fractal layout
/// Rules): only a folder whose name has UUIDv4's shape lowercase hex, `8-4-4-4-12` is a
/// lane/card *candidate* at those depths; see `isUUIDShaped` below for exactly what's checked.
/// Rules): only a folder whose name has a UUID's shape hex, `8-4-4-4-12`, **any case and any
/// version** is a lane/card *candidate* at those depths; see `isUUIDShaped` below for exactly
/// what's checked.
/// Anything else even a directory holding a perfectly valid `index.md` is a stray: skipped
/// with a `.nonUUIDFolderIgnored` warning, preserved verbatim on disk, and never descended
/// into. A hand-made `notes/` folder (or a broken `index.md` inside one) can never brick a
@@ -175,26 +176,40 @@ public enum BoardLoader: Sendable {
FileManager.default.fileExists(atPath: folder.appendingPathComponent(indexFileName).path)
}
/// The lowercase hex characters `isUUIDShaped` accepts in each `-`-delimited group.
private static let lowercaseHexDigits = Set("0123456789abcdef")
/// The hex characters `isUUIDShaped` accepts in each `-`-delimited group **both cases**,
/// per the shape-only identity predicate below.
private static let uuidGroupCharacters = Set("0123456789abcdefABCDEF")
/// Whether `name` has UUIDv4's shape lowercase hex, `8-4-4-4-12` gating lane/card level
/// detection (01-storage-format.md § Fractal layout Rules, "Name shape gates level
/// detection"). Deliberately permissive about *which* nibbles matter: the version (13th hex
/// digit) and variant (17th hex digit) are **not** validated, so any lowercase-hex string in
/// this shape reads as a candidate whether or not it was actually minted by
/// `UUID().uuidString.lowercased()`. That reading is intentional, not an oversight: the
/// loader's job is recognizing the folder-naming *convention*, not re-deriving RFC 4122
/// conformance every load. Case-sensitive an uppercase or mixed-case UUID string is a
/// stray, matching `ItemID`'s byte-perfect, never-normalized storage of the folder name
/// (`BoardModel.swift`).
/// Whether `name` has a UUID's shape hex, `8-4-4-4-12`, **any case and any version**
/// gating lane/card level detection (01-storage-format.md § Fractal layout Rules, "Name
/// shape gates level detection"). This is *the* identity predicate, and it is deliberately
/// **shape-only**: lowercase v4 is the app's emission rule, not the gate.
///
/// - **Any case.** `uuidgen(1)` and Swift's own `UUID().uuidString` both print *uppercase*,
/// so a strict lowercase gate would turn an agent's standard-tool card into a silently
/// skipped stray the worst failure mode for a files-first app. Accept liberally, emit
/// conservatively: `BoardWriter` still writes only lowercase v4 and never renames an
/// existing folder to canonicalize it.
/// - **Any version.** The version (13th hex digit) and variant (17th hex digit) nibbles are
/// **not** validated: they protect no invariant here an agent's v7 is exactly as unique
/// as a v4 and the loader's job is recognizing the folder-naming *convention*, not
/// re-deriving RFC 4122 conformance every load.
///
/// Equivalent to "does `UUID(uuidString:)` parse it", which is how 01-storage-format.md
/// states the rule; kept as a manual scan because that is the cheaper answer on the hot path
/// (every folder of every load) and needs no bridging.
///
/// Recognizing a name is not the same as *comparing* two of them: identity comparison is
/// UUID-*value* equality, so two case-spellings of one UUID are one identity everywhere
/// see `ItemID` (`BoardModel.swift`), which stores the folder's exact spelling but compares
/// canonically.
///
/// Internal rather than `private`: `BoardWriter.renumberVisibleChildren` walks the same
/// candidates the loader walked, and level detection has to be one rule, not two.
static func isUUIDShaped(_ name: String) -> Bool {
let groups = name.split(separator: "-", omittingEmptySubsequences: false)
guard groups.map(\.count) == [8, 4, 4, 4, 12] else { return false }
return groups.allSatisfy { $0.allSatisfy(lowercaseHexDigits.contains) }
return groups.allSatisfy { $0.allSatisfy(uuidGroupCharacters.contains) }
}
/// Direct subdirectories of `folder`, in deterministic (folder-name) order, excluding
@@ -310,7 +325,8 @@ public enum LoadWarning: Sendable, Equatable, CustomStringConvertible {
/// this case.
case missingIndex(path: String)
/// A lane/card-depth folder whose name doesn't have UUIDv4's shape (`isUUIDShaped`)
/// A lane/card-depth folder whose name doesn't have a UUID's shape (`isUUIDShaped` hex,
/// `8-4-4-4-12`, any case, any version)
/// skipped, not fail-fast, regardless of whether it holds a valid `index.md`, a broken one,
/// or none at all (01-storage-format.md § Fractal layout Rules, "Name shape gates level
/// detection"). Preserved verbatim on disk, never descended into. `path` is relative to the