SUBS-054: submodule pins as wtlog rows the parent appends the gitlink to the CHILD's wtlog (no-slash = the external pin)

Now
OPEN

Input

Context

A worktree tracks whatever it was told to track. For submodules, the parent makes its own get/post, takes the gitlink hash, and appends it to the CHILD's wtlog as an external pin the sub's local URI with NO trailing slash. The sub appends its own get/post records to the same log, with a trailing slash. Projects pin children, children pin grandchildren. Under the default course of events (full-depth commits, all changed subs) the two hashes are equal and the rows go in pairs get/get, post/post. An unpinned sub makes them differ; that difference is what status renders. Supersedes SUBS-040; implements URI-016 decisions 5/8/9/10/11.

ONE CHANGE, THREE TICKETS (gritzko, 2026-07-15): DIS-061 + URI-016 + this are parts of the same change and land together DIS-061 splits commit from branch-advance (the uniform flip), URI-016 removes the dot-branch and puts pins in wtlogs, SUBS-054 is the sub-side row scheme. Landing any one alone is what reddened 40 cases (verbs/post/post.js:726-738 backs the flip out as "unlanded WIP"). DIS-061's 2026-07-13 pin-branch EXEMPTION from the uniform rule dissolves here: with no pin-branches there is nothing to exempt, and the rule becomes literally uniform.

Goals

Constraints

WIP

Design decisions

-- gritzko (rulings, 2026-07-15)

  1. Both row kinds live in the SUB's own wtlog <wt>/<sub>/.be, interleaved by timestamp, told apart by PATTERN not by log (URI-016 decision 9).
  2. The trailing slash is the discriminator: ///path/to/sub?proj_branch#pin is the external pin (parent-written), ///path/to/sub/?branch#base is the sub's own record.
  3. The parent, on its OWN get/post, reads its gitlink hash and appends the pin row to the child's wtlog parents pin children, children pin grandchildren, one level each.
  4. The sub's own row is the worktree pair: tracked ref in the query, base commit in the fragment two different things, never conflated.

4a. A URI maps worktree/branch -> hash: the hash is ALWAYS the #fragment, the query is the tracked BRANCH. An empty query is not written at all (?#sha is a composer artifact of URI.make(…, branchKey || "", sha), not a tracked branch). 4b. Corner case (gritzko, 2026-07-15): in the DETACHED state the query may itself carry the hash (?7d37f90f, URI L50-51) the tracked ref IS a hash. Normal rows keep the hash in the fragment.

  1. Tracking the parent's pin is the normal case but not the only one (a sub may track a remote); same URI structure either way, the slash tells them apart.
  2. Pairs (get/get, post/post) are the default full-depth flow; divergence means the sub is unpinned, and status shows ahead/behind plus the post/miss lists.
  3. Conversion keeps the hashes and changes the authority/path only a dot-branch pin becomes the no-slash URI form of the same hash.
  4. An off-pin sub is left ALONE: the parent does not recurse get/patch into it and reports adv from gitlink != base (URI-016 decision 11).

TODOs

Blockers and bummers

Outcome