The four remote verbs (get/head/post/put) all turn on ONE question — the relative history of local cur vs a remote/branch tip (ahead/behind/diverged) — yet each answers it ad hoc, head is unimplemented, and post's non-FF refusal collapses every divergent/unrelated case into one misleading "use native be put to force" hint. The verdict is a GRAPH question, answerable WITHOUT downloading content packs: the push-side FF decision is a LOCAL parent-walk from cur against the advertised remote tip sha (dag.isAncestor over local objects — ALREADY correct in pushRemote); only the pull side (head/get) needs remote commits. Factor a shared advertise→resolve→verdict routine over dag.aheadBehind/ isAncestor, add head (report-only), route all four through it, make the non-FF path honest with no force hint on post. JS only (be/, via jab). Supersedes the discarded content-fetch spine. Method: work; siblings GIT-013/GIT-014/GIT-015.
verbs/post/post.js pushRemote (~line 308): dag.isAncestor(reader, old, tip) is a LOCAL parent-walk from cur (tip) seeking the advertised remote tip (old) — cur's ancestry is local, so it needs NO remote objects and never calls commitParents(old).POSTNOFF … use native be put to force, a destructive force hint even for a divergent/unrelated push it cannot (and does not) classify.shared/dag.js aheadBehind(keeper, cur, tip) → {ahead, behind} (commit rows, NO content) and isAncestor(keeper, anc, desc). There is NO dag.relate (the discarded spine invented it).shared/wire.js exports advertRefs/fetch/push/buildPushPack/classify; advertRefs(uri, verb) reads remote ref TIPS only, fetch pulls a content pack.post pushRemote (advert + isAncestor), get.js (wire.fetch then dag.aheadBehind, ~line 388), put its own path; head is NOT implemented (no verbs/head).old's connecting history as a CONTENT pack into .be/refs on EVERY push, then relate locally — conflated graph-negotiation with content-transfer and self-inflicted a cache-growth prune story. Dropped: content is never needed for the push verdict.advertRefs) → resolve target ref (the GIT-015 branch→refs/heads/X resolution) → verdict (dag.aheadBehind/isAncestor), consumed by all four verbs, computed once over objects actually present.post/put): FF/ahead decision is a LOCAL walk from cur vs the advertised remote tip; NO content download. Send only the ahead commits (all local). post is FF-only STRICTLY (a non-FF advance ALWAYS refuses, no force path); put is the force-allowed twin (set/reset any ref).head reports the ahead/behind commit LISTS + changed paths; get receives the behind commits' content.post refuses without guessing diverged/unrelated (that needs remote objects post will not fetch) and WITHOUT a force hint; put allows force.be/, run via jab. Do NOT run native be; do NOT open beagle/.dag.aheadBehind/isAncestor/ancestors, wire.advertRefs/fetch/buildPushPack/push/classify, the abc.index("wh128")/WHIFFKeyPack index bindings, the pushRemote ref-resolution. No new deps.GIT-016 comments, greppable, ticket-coded ≤64-char commit.wire.push sends old and parseReportStatus throws on non-fast-forward — the client verdict AUGMENTS, never replaces, that safety.push-ssh (FF), push-nonff (now an honest refusal), or get/fetch.{eq, ff(ahead), non-ff} from a local walk (cur's ancestry is local, the remote tip's is not — so behind is UNKNOWN and NOT guessed); the pull side, with remote commits present, gets full {eq, ahead, behind, diverged, unrelated}.wh128 DAG index (commit→parent edges), NEVER the pack log — enough for the verdict + ahead/behind lists. Mirrors graf's wh128 edge index (graf DAG.h).abc.index("wh128",{mem}) — a stack of sorted runs + memtable with newest-wins get/range/seek, GC'd at exit (js API.md) — keyed by WHIFFKeyPack(type, hashlet60); put(key,val) each fetched commit's parent edge. Same pattern as shared/store.js/ingest.js.dag.aheadBehind/isAncestor gain an OPTIONAL remote-index arg: resolve parents from local keeper.commitParents first, then the remote wh128 index (its run-stack already does newest-wins). Only head's changed-path diff needs remote trees/blobs (fetched transiently, unpersisted).get keeps wire.fetch + dag.aheadBehind for the behind list and persists to the pack log — it is the ONLY pack-log writer.post message first, then head, then reroute get/put.post refuses with an HONEST message (non-FF, no "be put to force" hint); an FF post still advances — over local objects, no content fetch. — test/wire/push-nonff-msg.shared/relate.js; shared/dag.js fns take an optional remote wh128 index. — landed 14924366; tests test/dag/remoteindex, test/relate/verdict.post/put through it: post FF-or-refuse STRICTLY (honest, no force hint), put set/force; local ahead commits only, then saveRemoteRef. — landed c12f6d4b (post.js relate spine, put.js shared resolveRef).head verb (verbs/head/head.js): in-memory remote DAG (no pack-log write), ahead/behind + changed paths (shared/changedpaths.js), remote-tracking refs only. — landed c12f6d4b; tests test/wire/head-bare/head-local/head-ssh/head-ssh-paths/head-cached.get through the shared routine for the behind list; get remains the sole pack-log writer. — landed c12f6d4b (verbs/get uses relate.verdict).push-ssh FF, push-nonff (honest path), get/fetch; wire + post buckets. — wire bucket green at land.head's fetched connecting commits live in an IN-MEMORY commit→parent DAG, never the pack log; changed-path trees/blobs are fetched transiently for the report and likewise unpersisted.post will not fetch to do so — the honest message says only "non-FF". Richer classification, if ever wanted, is a separate pull-side call.jab get file:/home/gritzko/.be?/beagle-ext; NEVER cp -a (the PUT-005 .be-redirect artifact).14924366 ("remote negotiation unified somewhat") + c12f6d4b ("remainder, tests and other things"), be/ trunk: shared/relate.js (advertise→resolve→verdict, POSTNOREF/POSTREF guards), shared/dag.js optional remote wh128 index, shared/changedpaths.js, verbs/head/head.js; post/put/get rerouted; honest POSTNOFF (no force hint).test/relate/verdict, test/dag/remoteindex, test/put/wire-refguard, test/wire/push-nonff-msg, test/wire/head-*, test/wire/saveremote.saveRemoteRef write is host-only).