shared/checkout.js apply materialises a missing object as empty
checkout.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.