Beagle SCM
GET-049: get stamps NOTHING — every file it writes must get the get row's ts as mtime
INVARIANT (gritzko, 2026-07-17): every file affected by get/patch and every file undirtied by post carries that wtlog record's ts as its mtime, so the stamp-set fast path (STATUS-011) confirms it clean with zero content reads. get is the outlier: NO io.setMtime site exists in the whole get path (put/patch/post all stamp, BE-011), so every fresh clone / checkout / update re-hashes wholesale on every status, forever. Sibling verify tickets: PATCH-013, POST-029.
Context
Found while profiling status (STATUS-011 Blockers): checkout mtimes are natural write times, not stamps, so wtlogReader.has(mtime) misses every get-written file.
be/verbs/get/get.js + its checkout path: zero setMtime calls; compare put.js:235 trySetMtime, patch.js:417, post.js:942 (BE-011).
- The get row ts is the stamp value: wtlog stamp-set membership is exact (
wtlog.js has(ts)), same policy as put's restamp-to-row-ts.
Goals
After any get (fresh clone, update, fast-forward), every file get WROTE has mtime == the get row ts; an immediate jab status does zero content reads on them (the STATUS-011 observable).
- Verify first: a repro test asserting the invariant on a fresh clone + an update — RED today.
- Fix: stamp each checkout-written file to the get row ts (one shared helper with put's restamp preferred).
Constraints
- Stamp ONLY files whose written bytes == the new baseline blob. A GET-048 merge OUTPUT (weave of pending changes) differs from base — stamping it would lie
ok; leave merge outputs unstamped (or route them to the DIS-057-style band as a follow-up ruling).
- The row must be appended BEFORE stamping (ULOGAppendAt refuses ts<=tail; same ordering discipline as DIS-057's band-under-ceiling).
- Untouched-by-get files keep their mtimes; deletes need no stamp.
WIP
Design decisions
- REJECTED (gritzko): reading the assigned ts back off the log
(tailTs) — no log read-back, ever; ts lives in the environment.
- be.now = the verb invocation's start ts, minted per run in
loop.js mintBe (resident procs re-sample); ambient.now() reads
it; get's ctx.T0 = ambient.now() — ONE ts for rows AND stamps,
reused across subs when the sub-mount follow-up lands.
- ulog.append now honours an explicit MONOTONIC ts verbatim
(bump only on use<=tail/prev, no silent bump-to-now) and
RETURNS the assigned ts per row — the collision-edge stamp.
POST-029 owns the canonical hunk; duplicated here byte-close.
- fanoutWholeTree pins ctx._get.stampTs = the assigned row ts;
null (restore/pick/mergeWorktreeTo: no row) = no stamp.
- Only the clean-overwrite leaf stamps (bytes == new blob);
mrg/con weave outputs + symlinks (setMtime follows) unstamped.
- Repro = test/get/stamp (run.sh + assert.js): mtime==row ts +
the statusfast io.open hook (0 reads), clone + update legs.
TODOs
Blockers and bummers
- The anticipated band follow-up ruled 2026-07-19: carried/woven
get outputs ride the DIS-057 band — GET-050 (stale
put stamps false-clean carried files; incident work/STATUS-016).
- Sub MOUNTS still unstamped: submount.mount/checkout.apply write
the sub wt with natural mtimes — the sub re-hashes; follow-up
(ruling: the SAME be.now stamps parent + every sub in one run).
- Same-tip re-get restamps nothing (leafUnchanged skips the
write): an mtime-churned clean wt stays slow until re-written.
- shared/ulog.js append() hunk is DUPLICATED with POST-029 (they
own the canonical one) — expect a weave dedup at merge time.
- Fresh-clone anchor row rides ronStepMs(T0,-1) (1ms before the
tip row's T0) so the tip ts == be.now exactly; cosmetic only.
Outcome