Realign read-side rules — width range coercion, finite order, symlink pins
The design corpus ratified that ranges are part of a sensible reading: an exact-integer width below 1 now coerces to 1 read-side (bytes untouched) instead of reading as malformed — the width division must never see a zero or negative unit — while a non-finite order (.nan, .inf) is now the same loud malformed-order rejection as a non-numeric one, guarded at the single point where the double arrives so loader and Writer inherit it together. The symlink-never-traversed rule turned out to be already enforced (the loader has filtered symlinks ahead of the directory check since the first commit); it and the copy-preserves-the-link-verbatim behavior are now pinned by tests, alongside the two hostile shapes the corpus names (width: 0, order: .nan). Five new tests. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -1382,6 +1382,46 @@ struct BoardWriterCopyTests {
|
||||
#expect(try fixture.entryNames("A.kanban/\(id.rawValue)/\(card)").contains("attachments"))
|
||||
}
|
||||
|
||||
/// A symlink surviving a copy as a symlink — never resolved, never followed to its target's
|
||||
/// bytes — is `FileManager.copyItem`'s own default behavior (01-storage-format.md § Fractal
|
||||
/// layout ▸ Rules, "Symlinks are never traversed": "Copy flows copy the link itself, never its
|
||||
/// target"). Nothing in `copyItem` arranges this on purpose — it falls out of leaning on
|
||||
/// `FileManager.copyItem` for the whole subtree rather than reading files one by one — so this
|
||||
/// pins the behavior against a future change (a different copy mechanism, a Foundation
|
||||
/// update) silently starting to dereference links instead.
|
||||
@Test func copyItemPreservesASymlinkVerbatimInsideACopiedSubtree() throws {
|
||||
let fixture = try WriterFixture()
|
||||
defer { fixture.tearDown() }
|
||||
try board(fixture)
|
||||
let cardPath = "A.kanban/\(Ident.lane1)/\(Ident.card1)"
|
||||
try fixture.file("\(cardPath)/attachments/real.png", Data([0x89, 0x50, 0x4E, 0x47]))
|
||||
try FileManager.default.createSymbolicLink(
|
||||
atPath: fixture.url("\(cardPath)/attachments/linked.png").path,
|
||||
withDestinationPath: "real.png"
|
||||
)
|
||||
|
||||
let id = try BoardWriter.copyItem(
|
||||
at: fixture.url("A.kanban/\(Ident.lane1)"),
|
||||
toParent: fixture.url("A.kanban"),
|
||||
order: nil,
|
||||
stamps: .fork
|
||||
)
|
||||
|
||||
let copied = try childrenByTitle(of: "A.kanban/\(id.rawValue)", in: fixture)
|
||||
let card = try #require(copied["Card One"])
|
||||
let copiedLink = fixture.url("A.kanban/\(id.rawValue)/\(card)/attachments/linked.png")
|
||||
|
||||
let values = try copiedLink.resourceValues(forKeys: [.isSymbolicLinkKey])
|
||||
#expect(values.isSymbolicLink == true)
|
||||
#expect(try FileManager.default.destinationOfSymbolicLink(atPath: copiedLink.path) == "real.png")
|
||||
// The source link is untouched too — a copy never touches what it reads from.
|
||||
#expect(
|
||||
try FileManager.default.destinationOfSymbolicLink(
|
||||
atPath: fixture.url("\(cardPath)/attachments/linked.png").path
|
||||
) == "real.png"
|
||||
)
|
||||
}
|
||||
|
||||
/// Template instantiation: born today, not forked — `created` and `modified` both fresh, and
|
||||
/// the same `Date` for the whole tree.
|
||||
@Test func bornStampsCreatedAndModifiedFreshAtEveryLevel() throws {
|
||||
|
||||
Reference in New Issue
Block a user