shared/checkout.js apply materialises a missing object as emptycheckout.apply's tree walk fetches each leaf's object and, on an UNRESOLVED
object (getObject → null), falls back to bytes = new Uint8Array(0) — then
writes a zero-byte file AND records a new/upd row anyway. The live
verbs/get/get.js:334 leaf does the right thing (if (!obj) return; — skip the
leaf, no row). Currently latent because apply is DEAD: it is exported but
neither get.js nor patch.js calls it (they use leafUnchanged/materialise/
scanWt). It becomes a real data-integrity bug — silent zero-byte truncation —
the moment apply is wired in. Method: Issues.
shared/checkout.js:142-143 const obj = keeper.getObject(leaf.sha); const
bytes = obj ? obj.bytes : new Uint8Array(0); — null object → empty bytes.:147-148 materialise(wtRoot, rel, leaf, bytes); rows.push({ verb: existed
? "upd" : "new", path: rel }); — writes the empty file + records a row
regardless of whether the object resolved.verbs/get/get.js:333-334 the live leaf: const obj = g.k.getObject(newSha);
if (!obj) return; — the correct early-return (skip, no row).shared/checkout.js:125 function apply(...) is exported (:168) but has
NO caller in verbs/get/get.js or verbs/patch/patch.js (both use
checkout.leafUnchanged/materialise/scanWt, never apply) — dead code.apply never writes empty bytes for an unresolved object: skip the leaf
(or throw), matching get.js:334. The well-known empty-blob sha (a real
0-byte file) is the ONLY case that legitimately writes nothing.apply cleanup: remove it, or fix-and-keep if it is the
intended checkout entry point.shared/checkout.js only; keep materialise/scanWt/leafUnchanged
(live callers) byte-identical. Pairs with the dead-apply cleanup decision.apply: const obj = keeper.getObject(leaf.sha); if (!obj)
return; before materialise — mirror get.js. Distinguish the empty-blob
sha (write 0 bytes, with a row) from a missing object (skip).apply + its export; document that get.js/patch.js own
materialisation. Confirm no out-of-tree caller first.apply (or its fixed twin) with a leaf whose sha is
absent from the store; assert NO zero-byte file is written and NO row.apply, or remove the dead apply + export.apply is now LIVE: shared/submount.js:273,320
call checkout.apply(k, pin, subWt, …) for submodule checkout (was dead in 2026-06-25).
The empty-bytes bug persists at shared/checkout.js:179-180 (const obj=keeper.getObject(leaf.sha);
const bytes = obj ? obj.bytes : new Uint8Array(0);), so a missing sub object now silently
writes a 0-byte file + a row. RESCOPE: skip-on-missing (the removal arm is off the table),
and this is effectively no longer LOW. The dead-apply removal in JS-090 is also now moot.