URI-016: unify URI->hash resolution across be/

Now
DONE

Pan-ticket: be/ has no ONE resolver turning a URI into an object. The record URI §URI->hash resolution advertises (store, mpath, shard, wtree, spath, rpath, chash, otype, ohash) is produced nowhere; each verb re-derives its own half. URI-013 unified the syntax layer (parse/compose via the URI class); this unifies the resolution layer. Umbrella over the per-site twins, gated on the URI-010 merge.

Input

Context

AUDIT 2026-07-14 (orchestrator) the FRAME half is largely BUILT, contrary to this ticket's first draft. core/discover.js already carries the spine, and most verbs are on it. Do NOT rebuild it; consolidate it and fill the two gaps.

Goals

One resolver, two halves, split at the seam the spec currently hides between steps 4 and 5.

Constraints

WIP

Design decisions

RULED 2026-07-14 by gritzko, in conversation; transcribed here.

  1. Drop ?/project. A worktree gives the shard; otherwise the store path does: file:/path/.be/shard. .be is the pivot prefix incl. .be = store, next segment = shard, rest = rpath. be:/https:/ssh: keep the git convention (path = repo). Cross-shard stays relative: //ABC-123/abc? = the sub's trunk.
  2. Resolution is against the tree at chash; the working copy is never consulted for the object half. A dirty file DOES resolve to the base blob, which is what diff names as its left side. Untracked / absent-at-chash is a plain failure.
  3. No partial records. Dirty/untracked are out of scope for the hash half, so otype/ohash are always defined on success.
  4. Precedence: fragment > query > worktree base. resolve_hash ignores ?branch when # is set; the VERB still consumes the branch track vs base are the worktree's two parameters ([the dirty-words post]).
  5. Pin-branches in default syntax; the synthetic dotted chain is REMOVED. The only thing the Title-rooted synthetic ref bought was showing a sub ahead/behind its parent's pin. Say that with a plain URI instead: ///path/to/sub?proj_branch#pin (main tree) or //worktree/path/to/sub#pin. A pin-branch is the gitlink value as a function of the parent's branch it MOVES as that branch moves, so it is ref-like yet DERIVED, never stored. ahead/behind = relate.verdict(sub's own chash, the resolved pin).
  6. A path at a sub root IS the sub root (RULED 2026-07-14, gritzko). //URI-016/test = the sub's ROOT TREE shard: "beagle-ext-tests", spath: "test/", rpath: "", otype: "tree". The parent's gitlink renders as an empty dir, and an empty dir has no value; nobody wants ls //URI-016/test to answer with a commit hash. A path AT a mount descends and re-anchors the frame exactly as a path THROUGH it does one rule, no boundary case.
  7. The pin is addressed EXPLICITLY, never derived from a bare path. Decision 5's row carries it in the fragment: ///path/to/sub#pin = the sub's frame at the parent's gitlink value. A pin IS a commit in the sub's shard, so decision 4 ("the fragment sets the frame's chash") applies unchanged no special case. Decision 8's refresh needs no URI at all: the parent walks its own tree on get/post and holds the gitlink already.

7a. Decision 1's gloss STANDS: //ABC-123/abc? is the SUB's trunk. (An earlier draft "corrected" it to the parent's trunk under a mount-boundary rule that decision 6 has now removed.)

  1. Pins live in WTLOGS, not the reflog. The parent refreshes the pin rows in its mounted subs' wtlogs on get/post. A reflog would have to hold a pin per (commit, sub) many commits, many pins, an all-history cache nobody needs. The wtlog holds only the CURRENT pin, i.e. mount state, not a cache.
  2. A mounted sub is therefore an ORDINARY worktree with the worktree two parameters BOTH recorded in the SUB's OWN wtlog as get/post rows, told apart by PATTERN, not by log:
  3. The row patterns, concretely. get SETS the current branch GET-OWNED, per SUBS-040's surviving ruling and Worktree: a bare get FFs the tip but KEEPS the branch, and post/put/delete advance the tip yet NEVER switch branch. Local commits land as post #hash rows (the base advances). The parent's get/post writes the ///path#pin row (the tracked pin) but ONLY when the sub is freshly cloned or SITS ON the pin.
  4. An off-pin sub is left ALONE. When the sub is off-pin the parent does NOT recurse get/patch into it "you know better" and reports it adv from gitlink != base. This is what makes the decision-8 refresh safe: the parent never clobbers a sub you are working in. It also supersedes SUBS-040's I3 (ahead/behind measured against the dot-branch) the answer is now gitlink != base, no ref involved.

TODOs

Spec fixes first (they are cheap and they pin the code's target); URI is -- gritzko hand-written, so those need his hand or his say-so.

Blockers and bummers

Outcome

WIP 2026-07-14 FIVE slices COMBINED in wt //URI-016, uncommitted, pending gritzko's inspect/merge. Suite *168 pass / 10 fail*, and all 10 fail on main too (bro/color, bro/view, cat/links, dag/remoteindex, get/http, get/oddname, put/wire-refguard, relate/verdict, sub/wtsrc, wire/saveremote); 8/8 JS units; 6 new repros green. NO red left that this work introduced the two that encoded the superseded TUI rule were amended under gritzko's 2026-07-14 "amend both". Slice 5 = BRO-022 goal 1 (views/bro/pager.js only): _fsCompletions/_glob readdir the VIEW's authority tree via a new _ctxRoot() seam over be.wtdir, not the launch tree repro test/bro/tabtree (two hive trees with DISJOINT filenames, so a candidate can only come from the tree actually read). Pre-fix the launch tree's names leaked into BOTH a //OTHER view and a REMOTE view. Null wtdir (scheme/unresolvable) no FS candidates (hunk candidates still serve); a SCOPE-LESS ctx keeps the launch-tree fallback. BRO-022 goal 2 was already subsumed by slice 4's _compTok. TUI and CLI now resolve a bare arg by the SAME rule (probed live, context //WT/verbs/post: bare post.js -> //WT/verbs/post/post.js in the pager, verbs/post/post.js in the CLI; /post.js -> wt-root in both). TWO EXISTING TESTS FAIL BY DESIGN pending a gritzko ruling (see the bare-path entry above) bro/pager (tab-nested-wtrel asserts the superseded wt-root token) and bro/glob (stale _verbUri fixture). NEITHER was edited (work 4/5). All other failures are pre-existing on main. Suite: main 163 pass / 12 fail vs combined 165 / 10 identical failures minus this ticket's two repros; 8/8 JS units. The 10 are pre-existing on BOTH sides (bro/color, bro/view, cat/links, dag/remoteindex, get/http, get/oddname, put/wire-refguard, relate/verdict, sub/wtsrc, wire/saveremote).

Still open: the CLI-vs-TUI bare-path ruling (above), decision 8's post-side pin refresh, decision 11's recursion half, the legacy-row migration (must land WITH its consumer), and the hash half (chash/otype/ohash + the one record) this ticket's original core, untouched by these three slices.