_fsCompletions) escapes the wt via uncollapsed ../views/bro/pager.js _fsCompletions(stem) builds dir = wt_root[/viewPath] +
"/" + sub by RAW concatenation from a user-typed Tab-completion stem, stripping
only a leading ./ and trailing / — .. segments pass straight into
io.readdir / io.lstat. So Tab-completing ../../… enumerates directories
ABOVE the worktree / SRC_ROOT. Same class as BE-011 (concat + uncollapsed
..), in the readdir path; found in the BE-011 follow-up wt-open audit. The
sibling ad-hoc .. calculators joinPath (:228, silently CLAMPS with
segs.pop()) and _resolveSpell (:271, keeps ../x VERBATIM) feed the same
surface with inconsistent semantics. Method Issues.
pager.js:612-613 dir = base + "/" + sub, :619 io.lstat(dir + "/" + nm); sub = completion stem with only ^\./ and /+$ stripped, NOT ..-collapsed.joinPath (:228) collapses ./.. but silently clamps at root (no refusal); _resolveSpell (:309) passes ../x through verbatim — both diverge from resolveInTree's throw-on-escape.driveSpell → discover.resolve() DOES throw NAVESCAPE, but _fsCompletions opens the fs DIRECTLY, before any of that... that would climb above the wt yields NO completions (catch NAVESCAPE → []), never a readdir/lstat outside the tree._fsCompletions through resolveInTree(viewPath, sub) / wtJoin under wt_root.joinPath/_resolveSpell .. handling onto resolveInTree (one semantics).../../ completion stem must not enumerate outside the wt).shared/util/path.js. Same tree (no worktree)._fsCompletions: dir = wtJoin(root, resolveInTree(viewPath, sub)). The stem's
sub is resolved against the VIEW path (base = viewPath, not "") so a ./-relative
completion climbs from the on-screen dir; resolveInTree THROWS NAVESCAPE on any climb
above the wt root → caught → [] (NO readdir/lstat outside the tree). io.lstat on
an entry now goes through path.join(dir, nm), no raw concat.joinPath now delegates to resolveInTree (ONE .. semantics): it collapses ./..,
PRESERVES a leading "/", and THROWS on a climb above root — no more silent clamp._resolveSpell's view-dir branch routes ././x/../x through joinPath, so ../x
is COLLAPSED (was kept verbatim). Spells THROW rather than clamp — consistent with the
downstream discover.resolve() NAVESCAPE (a _runSpell try/catch shows it in the addr bar).../ completion stem escapes today (RED). test/bro/navescape/navescape.js._fsCompletions via resolveInTree/wtJoin; unify joinPath/_resolveSpell._fsCompletions.views/bro/pager.js (uncommitted — working tree left dirty for review):
require("shared/util/path.js"); _fsCompletions path composition confined via
resolveInTree/wtJoin (NAVESCAPE → []); joinPath + _resolveSpell .. handling
unified onto resolveInTree (collapse + throw, no clamp / no verbatim ../x).test/bro/navescape/navescape.js: RED before (returned ../OUTSIDE/secret_outside),
GREEN after. Regression green: test/bro/pager, test/bro/ticket, test/uri.js.test/bro/color + test/bro/view golden
mismatches are readdir-ORDER diffs in the non-interactive dir sink (functions untouched
here are not on that path).