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