be get silently overwrites put/patch-staged content
be get <target> destroys uncommitted STAGED work: a path staged by be put/be patch is OVERWRITTEN with the target's bytes when the target also changed it — no refusal, no merge, exit 0. The dirty-overlap protection in get_overlap_step (sniff/GET.c) only weave-merges/refuses for an UNATTRIBUTED mtime; staged content is STAMPED (known mtime), so the classifier skips protection and the WRITE pass clobbers it. Cost this session: landing JS-036 onto JS-035 lost the JABCdelta registration + encode docs (commit 742c06ff shipped incomplete). Repro + contrast + session evidence in this dir. Method: Issues.
get_overlap_step (sniff/GET.c:440-529) routes a CHANGED path
to weave-merge (GET_V_MRG, :515) or refusal (:460) ONLY when !SNIFFAtKnown(mr) (:460/:480). A stamped path skips the block, hits return OK (:529) with no merge/noop/unlink row → WRITE overwrites it.
be put/be patch stamp the files they write, so theirstaged (uncommitted) bytes look "clean baseline" to the classifier, not dirty.
f=base → c1 f=theirs;
be get ?#c0; echo mine>f; be put f; be get ?#c1 → upd, f=theirs, rc 0 (mine LOST). Contrast (UNSTAMPED edit, no be put) → mrg, f=<<<<mine||||theirs>>>> (preserved). Same input, opposite outcome.
be patch ?! staged
merged js/{API.md,CMakeLists.txt,INDEX.md} (pat), then be get flipped them to upd, discarding JS-036's edits — see JS-036. (Same capture as BE-008, a different bug.)
be get must NOT silently destroy put/patch-staged content for a path thetarget changed: weave-merge it (as the unstamped arm does) or refuse clearly.
--force (be get!) stays the explicit opt-in to overwrite dirty/staged bytes.sniff/GET.c overlap classifier; keep the unstamped weave-merge + no-op-skip
intact; don't regress test/get/* (esp. 49 force-reset, 09/10 atomicity).
put/patch row's recorded blob sha (uri.fragment).
get_overlap_step, a CHANGED path stamped by a PENDING put/patch row
(after the pd boundary, not a baseline get/post) must route to the dirty arm: schedule GET_V_MRG against the staged baseline, or refuse — distinguish it from a path stamped by a baseline get/post (safe to overwrite).
test/get/<n> (both arms): staged → merge/refuse,
unstamped → still merge. Repro-first (CLAUDE.md §17). → test/get/66-staged-overlap-merge (8a1f8413).
get_overlap_step to detect pending put/patch ownership vs baseline
get/post and route to merge/refuse; --force overwrites. → get_pending_stage_stamp + dirty flag (8a1f8413).
test/get/49, 09, 10 green. → all green + full ctest 471/471 (see Outcome).GET-022 (per-file restore must not clobber WIP), GET-018 (atomic wt GET).
8a1f8413* (test/get/66-staged-overlap-merge). be get
no longer silently overwrites put/patch-staged content for a path the target also changed: such a path is now weave-merged (same arm as the unstamped case), and --force (be get!) remains the explicit opt-in to overwrite it.
sniff/GET.c, get_overlap_step): the three
dirty-protection branches keyed on !SNIFFAtKnown(mr) now key on a single b8 dirty = !SNIFFAtKnown(mr) || get_pending_stage_stamp(mr). The unstamped weave-merge + clean-drift no-op-skip + --force overwrite arms are untouched — a pending-staged path simply enters the same arms an unattributed mtime already did.
get_pending_stage_stamp, new static
helper): for a path whose mtime IS in the stamp-set, look up the owning ULOG row via SNIFFAtRowAtTs(mtime,…). YES (dirty/staged) iff the verb is put/patch/delete AND the row's ts is strictly above SNIFFAtPatchFloorTs() (the latest get/post anchor) — i.e. a pending stage after the boundary. A get/post stamp, or a stale put/patch at/below the floor (already committed / reset away), returns NO → safe to overwrite, unchanged behaviour. The merge baseline stays the base-tree leaf blob (base->uri.fragment, the common ancestor the user get'd before staging); a plain be put row carries an empty fragment, so the base-tree blob — not the put row — is the correct weave ancestor, and the staged wt bytes are read from disk by get_drain_merges.
repro.sh into
test/get/66-staged-overlap-merge/run.sh (auto-discovered by the */*/run.sh glob; ctest be-get-66-staged-overlap-merge). ARM A (staged via be put) must mrg/refuse with mine preserved; ARM B (unstamped raw edit) must still mrg.
upd f.txt /
f.txt = [theirs] (staged bytes LOST, rc 0) while ARM B mrg'd (verified by reverting dirty to the bare !SNIFFAtKnown(mr) and rebuilding). Post-fix, both arms mrg and preserve mine; test passes under build/ and build-debug/ (ASan).
test/get/49 (force-reset), test/get/09 &
test/get/10 (atomicity) all green; full be-get-* (78), the put/patch/post/del families (114), and the whole ctest suite (471/471) pass in build/, and get/put/patch (92) pass under build-debug/ ASan.
sniff/GET.c (new get_pending_stage_stamp
helper + the dirty-flag refactor of the three branches); test/get/66-staged-overlap-merge/run.sh (new two-arm case). No be/ changes (concurrent JS-065 worker).