Skip to content

Two canonicalisers that disagree on the #181 case: unify canonicalRoot onto paths.Canonical #273

Description

@golimpio

Summary

There are now two functions in the tree that answer "what is the canonical spelling of this path?", they are documented as if each were the only one, and they disagree on the exact case that caused #181.

paths.Canonical (internal/paths/canonical.go) canonicalRoot / canonicalPathForBoundary (internal/tools/boundary.go)
Relative path Cleaned and returned unchanged, no filesystem access Anchored to the process cwd via filepath.Abs
.. handling Handed to EvalSymlinks uncleaned, so .. resolves in kernel order filepath.Abs Cleans first, collapsing .. lexically
Missing path Nearest existing ancestor resolved, tail re-joined Same
Failure Degrades to filepath.Clean Same

Both were arrived at deliberately and both are currently correct for their own caller. The problem is that neither says it is one of two, so the next person to need canonicalisation will pick whichever they find first.

Why the divergences matter

Relative paths. paths.Canonical refuses to anchor them, because the daemon is a singleton whose working directory belongs to whichever client happened to spawn it — the silent cross-repository write of #181/#182. canonicalRoot anchors them with filepath.Abs. That is safe where it is used today (PathPolicy compares already-absolute roots), but it is exactly the wrong default to inherit by copy-paste.

Lexical ... canonicalPathForBoundary calls filepath.Abs before EvalSymlinks, and Abs Cleans — so a .. following a symlink is collapsed lexically, which diverges from the kernel's left-to-right resolution. This is the divergence #264 is landing a refusal for. paths.Canonical avoids it by passing the uncleaned path to EvalSymlinks, which resolves components in kernel order.

Suggested shape

Once #264 has landed:

  1. canonicalRoot delegates to paths.Canonical, so there is one definition of "same place".
  2. The unresolved-.. refusal stays where fix(tools): refuse an absolute path whose ".." the kernel resolves differently #264 puts it, in PathPolicy.Check, above the canonicaliser. That separation is the right one and must not be undone by the graft: Canonical is an identity function, not an authorisation check.
  3. Decide explicitly what the absolute-anchoring behaviour should be at the boundary seam, rather than inheriting it. If PathPolicy genuinely needs a relative path anchored, it should do that itself, visibly, before calling the shared canonicaliser.
  4. Add a rule to internal/arch.PrimitiveRules pinning the shared canonicaliser, so it cannot silently re-fork. PrimitiveRules exists for precisely this discipline and currently has no entry covering it.

Provenance

Split out of #270 (the #263 fix), which introduced paths.Canonical and deliberately did not touch internal/tools/boundary.go because #264 was in flight in that file and lands a property the graft must not undercut. Raised as a NIT by an independent review of #270, with the PrimitiveRules observation.

Not urgent and not a live defect — a trap for the next change, which is the cheapest kind of thing to fix before it fires.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    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