Beagle SCM
WORK-007: work forest links by TRACKING edge — tracked == upstream
Context
Live 2026-07-19: the work view hangs //WHY-002 under the store although its wtlog ends get //WHY#43a754a0… — it TRACKS the //WHY wt. Root cause (be tip, views/work/work.js): classify() resolves the pin (be.wtdir) but looks the target up in indexNodes' byWt (:199), which holds ONLY the context-tree skeleton (top wt + gitmodules mounts, nodeAt :156); tracker wts are rows, never nodes, so a wt tracking another work/ wt has nowhere to hang and the "Unresolvable pin" fall-through (:244 — mislabeled, the pin DID resolve) demotes it to block 2 by its store anchor.
- The WORK-001 model is ONE level deep: trackers hang only off main-tree nodes (the node.wts runs).
- attachedBranch (shared/wtlog.js:161) already yields the track edge per wt: URI/wt pin, branch/trunk, or detached.
Goals
RULING (gritzko, chat 2026-07-19): the forest IS the tracking relation — tracked == upstream; every wt hangs under the tree its recentmost get row tracks, RECURSIVELY (work wts are nodes too: WHY-002 → WHY → ///be).
- Pin vs base placement:
///be/test (slashless) is the GITLINK pin recorded in ///be — a wt tracking that pin goes under ///be; ///be/test/ (trailing slash) is the ///be/test wt's own base commit — that tracker goes under ///be/test (Submodules, de-jure pin vs de-facto rev).
- A mount's upstream is its parent's gitlink pin, so mounts keep nesting under the parent tree; WORK-001's render rulings (solid vs dotted rails, buttons, ahbeh, fade) ride unchanged.
- Branch trackers land under store → shard → branch, detached wts under the shard, foreign anchors in the foreign block — the WORK-001 hunks stand; only the LINKING model changes.
Constraints
- Supersedes WORK-001's mounts-only attach (classify/indexNodes, views/work/work.js:199-264); WORK-001 stays the umbrella view ticket — crosslinked.
- ALL track/URI resolution via wtlog.attachedBranch + core resolve/wtdir — no regexes, no string surgery; the trailing-slash semantics must come from the resolver, not lexical checks.
- Overlaps the UNLANDED work/WORK-003/004/005/006 wts in views/work/work.js + test/work/view — build on be tip, expect weaves.
- Rulings in force: pty verification through the real UI path; errors in plain words; use URIs to name trees in reports.
WIP
Design decisions
- (pending) hunk-1 root: does the top wt stay the root (its own upstream — a store branch — NOT rendered as a parent), or does the forest unify under store→shard→branch?
- (pending) a cycle in the track relation (A tracks B, B tracks A): proposed — break at the name-sorted first node, mark the row in plain words.
- (pending) a track target resolving to NO live tree (deleted wt, work/done/): proposed — fall back to the anchor shard with a plain-words mark, never silently to the store.
TODOs
- Defaults (b) cycle-break and (c) dead-target implemented defensively, marked in plain words, not separately unit-tested
Blockers and bummers
- The classify/indexNodes region churns across four unlanded wts (WORK-003..006) — landing order will force weaves.
- RESOLVED 2026-07-19: the clones materialized without the work view because //WORK tracked slashless
///be — the journal's stale de-jure PIN (#43a754a0, pre-forest) — while the forest was landed live in ///be/ (27d03a26 WORK-004+006, e4614c19 WORK-005, tip 9928a0be); a live instance of this ticket's pin-vs-base subject. Fixed by jab get '///be/' in //WORK + bare get in //WORK-007 (gritzko-authorized); WORK-003 remains the only unlanded overlap.
Outcome
- Staged (put only, NOT posted) in //WORK-007 on base //WORK/#9928a0be: views/work/work.js (~412-line diff: resolveTrack, per-wt nodes, placeWt/breakCycles two-pass, recursive pushWt) + test/work/view/run.sh in the test sub (WORK-007 fixture: slashless pin, slashed base, wt→wt chain).
- Suggested commit:
WORK-007: work forest links by tracking edge, pin/base slash
- Side find, unfiled: test/work/getctx/getctx.py drives the
[get] button retired by WORK-004 — red on the pristine base; retarget it to [-N]/[diff] when WORK-002 rework lands.