Skip to content

fsroot.Path.Rel() returns OS-native separators, making serialized documents and checksums platform-dependent #377

Closed

Description

@David-Noble-at-work

fsroot.Path.Rel() returns OS-native separators, so the same logical path serializes as sub/file.txt on Linux and sub\file.txt on Windows. Since Path is a serialized type, that makes documents and their checksums platform-dependent.

Evidence

From the first three-platform CI run (PR #375, commit f8b28c82, job test (windows-latest)), six failures of this shape:

root_test.go: Rel() = "sub\\file.txt", want "sub/file.txt"
root_test.go: Rel() = "b\\c.txt", want "b/c.txt"

Mechanism

Path.Rel() returns the stored rel field (pkg/fsroot/root.go:544). That field is built with OS-native helpers:

  • pkg/fsroot/root.go:687 — rel := assert.Must(filepath.Rel(rootName, path))
  • pkg/fsroot/root.go:692 — rel: filepath.Clean(path)

filepath.Rel and filepath.Clean both yield \ on Windows.

Why this is a defect rather than a test expectation

Path round-trips through serialization — pkg/fsroot/root.go:624 and pkg/fsroot/root.go:659 both assign p.rel = decoded.Rel when decoding. So rel is not merely an in-memory convenience; it is written into documents and read back.

The consequence is that a graph, trace, or receipt produced on Windows differs byte-for-byte from the identical logical result produced on Linux, and therefore checksums differ. This repository already treats that as unacceptable elsewhere and specifies its directory-digest Merkle root as forward-slash, NUL-delimited, and sorted precisely so the digest is platform-stable. Path.Rel() violates the same principle.

The six failing assertions are asserting the correct thing. The product is what is wrong.

Likely shape of the fix

Normalize to slash form at construction — filepath.ToSlash on the results at root.go:687 and root.go:692 — so rel is canonical regardless of host, converting back with filepath.FromSlash only where an OS call requires it. abs may legitimately stay OS-native since it is a local handle rather than a serialized identity; that distinction needs confirming rather than assuming, along with an audit of every consumer of Rel() and every existing serialized document.

Found by the platform test matrix (#373) on its first run. Classified bucket 1 — a genuine platform defect — under that plan's phase 2 triage.

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions